Completed
Push — master ( ae5621...ef667c )
by Julito
13:23
created

learnpath   F

Complexity

Total Complexity 1820

Size/Duplication

Total Lines 13303
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13303
rs 0.6314
c 0
b 0
f 0
wmc 1820

203 Methods

Rating   Name   Duplication   Size   Complexity  
D edit_item_prereq() 0 46 10
A getProgressBar() 0 5 1
A get_author() 0 9 3
A getHideTableOfContents() 0 3 1
A get_progress_bar() 0 12 1
A get_theme() 0 9 3
A get_progress_bar_mode() 0 9 3
C get_preview_image_path() 0 28 7
A getProgress() 0 22 2
C save_item() 0 46 9
C save_last() 0 48 10
D set_current_item() 0 33 10
B save_current() 0 32 6
B restart() 0 40 6
B get_iv_interactions_array() 0 42 4
B set_encoding() 0 23 5
A get_objectives_count_from_db() 0 16 2
A get_total_items_count() 0 7 2
A get_first_item_id() 0 8 2
A get_course_int_id() 0 3 2
A get_last() 0 13 3
B getSiblingDirectories() 0 35 6
B get_brother_items() 0 31 6
A set_course_int_id() 0 3 1
A get_js_lib() 0 8 2
A get_id() 0 6 2
A getTotalItemsCountWithoutDirs() 0 14 4
F add_lp() 0 147 14
A getCourseCode() 0 3 1
A get_common_index_terms_by_prefix() 0 17 3
C get_package_type() 0 79 19
B get_complete_items_count() 0 27 6
A close() 0 16 3
F autocomplete_parents() 0 105 18
C get_navigation_bar() 0 67 8
B get_iv_objectives_array() 0 34 3
A getCategory() 0 7 1
B getCalculateScore() 0 47 6
A get_update_queue() 0 7 2
B copy() 0 26 1
A getAccumulateScormTime() 0 3 1
F create_document() 0 171 30
A previous() 0 14 2
A tree_array() 0 7 2
A createCategory() 0 8 1
B move_down() 0 54 8
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
B display_lp_prerequisites_list() 0 31 5
B getForum() 0 44 3
A getExercisesItems() 0 13 3
A getCountCategories() 0 10 2
A get_preview_image() 0 9 3
A get_teacher_toc_buttons() 0 21 4
A getItem() 0 7 3
A getCategoryId() 0 3 1
B get_links() 0 109 6
A toggle_visibility() 0 14 2
B create_path() 0 14 5
B upload_image() 0 40 6
A getChapterTypes() 0 4 1
D rl_get_resource_name() 0 99 14
A getSavedFinalItem() 0 12 3
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 14 3
A getSubscribeUsers() 0 3 1
B deleteCategory() 0 32 4
A moveUpCategory() 0 11 2
D print_recursive() 0 39 10
C isFirstOrLastItem() 0 36 7
C getCalculateStars() 0 80 12
B get_scorm_xml_node() 0 19 7
A has_audio() 0 14 4
A getFinalEvaluationItem() 0 12 3
B build_action_menu() 0 117 5
B get_view() 0 41 6
A getCategoryByCourse() 0 8 1
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B delete_lp_image() 0 17 5
A getFinalItem() 0 12 4
A getFinalItemTemplate() 0 3 1
F getListArrayToc() 0 79 12
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_lp_session_id() 0 9 3
F exportToCourseBuildFormat() 0 197 30
B get_toc() 0 27 5
C categoryIsVisibleForStudent() 0 68 13
B get_student_publications() 0 44 4
A get_name() 0 9 3
C update_default_view_mode() 0 40 8
B next() 0 22 5
D display_edit_item() 0 120 17
B set_autolaunch() 0 27 2
F display_thread_form() 0 216 42
A getCategoryFromCourseIntoSelect() 0 15 4
F get_link() 0 319 57
B switch_attempt_mode() 0 21 5
C prerequisites_match() 0 67 16
B get_exercises() 0 104 5
C fixBlockedLinks() 0 64 11
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 55 18
C start_current_item() 0 40 16
B set_publicated_on() 0 30 5
A set_prerequisite() 0 16 3
A set_modified_on() 0 16 3
B categoryIsPublished() 0 26 2
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
C toggle_publish() 0 80 11
A moveDownCategory() 0 11 2
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 224 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
C isBlockedByPrerequisite() 0 32 8
F get_progress_bar_text() 0 43 11
D first() 0 68 20
F create_tree_array() 0 41 12
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
A get_current_item_id() 0 14 4
A set_previous_item() 0 6 2
B getCurrentBuildingModeURL() 0 10 5
A display_document() 0 20 2
A setSubscribeUsers() 0 13 2
A set_theme() 0 17 3
F __construct() 0 336 54
A set_author() 0 16 3
B set_attempt_mode() 0 32 5
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
C move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
C get_scorm_prereq_string() 0 73 11
D display_item_prerequisites_form() 0 177 16
A clear_prerequisites() 0 17 2
C scorm_export_to_pdf() 0 63 12
C display_move_item() 0 55 11
F display_hotpotatoes_form() 0 184 39
B get_js_info() 0 44 6
B get_js_dropdown_array() 0 51 6
B get_next_item_id() 0 20 6
F is_lp_visible_for_student() 0 127 22
B get_flat_ordered_items_list() 0 35 5
A display_resources() 0 49 1
B verify_document_size() 0 21 8
F display_manipulate() 0 115 15
A display_item_small_form() 0 13 1
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
A get_extension() 0 5 1
D rl_get_resource_link_for_learnpath() 0 168 31
B getLpFromSession() 0 28 5
A set_hide_toc_frame() 0 20 4
D get_next_index() 0 32 10
F return_new_tree() 0 415 49
A setCategoryId() 0 10 1
A get_previous_item_id() 0 8 2
A get_level_for_item() 0 11 3
D delete() 0 110 18
A set_maker() 0 20 4
B lpHasForum() 0 26 1
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\CourseArchiver;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
9
use Chamilo\CourseBundle\Entity\CItemProperty;
10
use Chamilo\CourseBundle\Entity\CLp;
11
use Chamilo\CourseBundle\Entity\CLpCategory;
12
use Chamilo\CourseBundle\Entity\CLpItem;
13
use Chamilo\CourseBundle\Entity\CLpItemView;
14
use Chamilo\CourseBundle\Entity\CTool;
15
use Chamilo\UserBundle\Entity\User;
16
use ChamiloSession as Session;
17
use Gedmo\Sortable\Entity\Repository\SortableRepository;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\Finder\Finder;
20
21
/**
22
 * Class learnpath
23
 * This class defines the parent attributes and methods for Chamilo learnpaths
24
 * and SCORM learnpaths. It is used by the scorm class.
25
 *
26
 * @todo decouple class
27
 *
28
 * @package chamilo.learnpath
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    public $attempt = 0; // The number for the current ID view.
36
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
37
    public $current; // Id of the current item the user is viewing.
38
    public $current_score; // The score of the current item.
39
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
40
    public $current_time_stop; // The time the user closed this resource.
41
    public $default_status = 'not attempted';
42
    public $encoding = 'UTF-8';
43
    public $error = '';
44
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
45
    public $index; // The index of the active learnpath_item in $ordered_items array.
46
    public $items = [];
47
    public $last; // item_id of last item viewed in the learning path.
48
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
49
    public $license; // Which license this course has been given - not used yet on 20060522.
50
    public $lp_id; // DB iid for this learnpath.
51
    public $lp_view_id; // DB ID for lp_view
52
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
53
    public $message = '';
54
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
55
    public $name; // Learnpath name (they generally have one).
56
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
57
    public $path = ''; // Path inside the scorm directory (if scorm).
58
    public $theme; // The current theme of the learning path.
59
    public $preview_image; // The current image of the learning path.
60
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
61
62
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
63
    public $prevent_reinit = 1;
64
65
    // Describes the mode of progress bar display.
66
    public $seriousgame_mode = 0;
67
    public $progress_bar_mode = '%';
68
69
    // Percentage progress as saved in the db.
70
    public $progress_db = 0;
71
    public $proximity; // Wether the content is distant or local or unknown.
72
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
73
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
74
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
75
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
76
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
77
    public $user_id; //ID of the user that is viewing/using the course
78
    public $update_queue = [];
79
    public $scorm_debug = 0;
80
    public $arrMenu = []; // Array for the menu items.
81
    public $debug = 0; // Logging level.
82
    public $lp_session_id = 0;
83
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
84
    public $prerequisite = 0;
85
    public $use_max_score = 1; // 1 or 0
86
    public $subscribeUsers = 0; // Subscribe users or not
87
    public $created_on = '';
88
    public $modified_on = '';
89
    public $publicated_on = '';
90
    public $expired_on = '';
91
    public $ref = null;
92
    public $course_int_id;
93
    public $course_info = [];
94
    public $categoryId;
95
96
    /**
97
     * Constructor.
98
     * Needs a database handler, a course code and a learnpath id from the database.
99
     * Also builds the list of items into $this->items.
100
     *
101
     * @param string $course  Course code
102
     * @param int    $lp_id
103
     * @param int    $user_id
104
     */
105
    public function __construct($course, $lp_id, $user_id)
106
    {
107
        $debug = $this->debug;
108
        $this->encoding = api_get_system_encoding();
109
        if ($debug) {
110
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
111
        }
112
        if (empty($course)) {
113
            $course = api_get_course_id();
114
        }
115
        $course_info = api_get_course_info($course);
116
        if (!empty($course_info)) {
117
            $this->cc = $course_info['code'];
118
            $this->course_info = $course_info;
119
            $course_id = $course_info['real_id'];
120
        } else {
121
            $this->error = 'Course code does not exist in database.';
122
        }
123
124
        $lp_id = (int) $lp_id;
125
        $course_id = (int) $course_id;
126
        $this->set_course_int_id($course_id);
127
        // Check learnpath ID.
128
        if (empty($lp_id) || empty($course_id)) {
129
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
130
        } else {
131
            // TODO: Make it flexible to use any course_code (still using env course code here).
132
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
133
            $sql = "SELECT * FROM $lp_table
134
                    WHERE iid = $lp_id";
135
            if ($debug) {
136
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
137
            }
138
            $res = Database::query($sql);
139
            if (Database::num_rows($res) > 0) {
140
                $this->lp_id = $lp_id;
141
                $row = Database::fetch_array($res);
142
                $this->type = $row['lp_type'];
143
                $this->name = stripslashes($row['name']);
144
                $this->proximity = $row['content_local'];
145
                $this->theme = $row['theme'];
146
                $this->maker = $row['content_maker'];
147
                $this->prevent_reinit = $row['prevent_reinit'];
148
                $this->seriousgame_mode = $row['seriousgame_mode'];
149
                $this->license = $row['content_license'];
150
                $this->scorm_debug = $row['debug'];
151
                $this->js_lib = $row['js_lib'];
152
                $this->path = $row['path'];
153
                $this->preview_image = $row['preview_image'];
154
                $this->author = $row['author'];
155
                $this->hide_toc_frame = $row['hide_toc_frame'];
156
                $this->lp_session_id = $row['session_id'];
157
                $this->use_max_score = $row['use_max_score'];
158
                $this->subscribeUsers = $row['subscribe_users'];
159
                $this->created_on = $row['created_on'];
160
                $this->modified_on = $row['modified_on'];
161
                $this->ref = $row['ref'];
162
                $this->categoryId = $row['category_id'];
163
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
164
165
                if (!empty($row['publicated_on'])) {
166
                    $this->publicated_on = $row['publicated_on'];
167
                }
168
169
                if (!empty($row['expired_on'])) {
170
                    $this->expired_on = $row['expired_on'];
171
                }
172
                if ($this->type == 2) {
173
                    if ($row['force_commit'] == 1) {
174
                        $this->force_commit = true;
175
                    }
176
                }
177
                $this->mode = $row['default_view_mod'];
178
179
                // Check user ID.
180
                if (empty($user_id)) {
181
                    $this->error = 'User ID is empty';
182
                } else {
183
                    $userInfo = api_get_user_info($user_id);
184
                    if (!empty($userInfo)) {
185
                        $this->user_id = $userInfo['user_id'];
186
                    } else {
187
                        $this->error = 'User ID does not exist in database #'.$user_id;
188
                    }
189
                }
190
191
                // End of variables checking.
192
                $session_id = api_get_session_id();
193
                //  Get the session condition for learning paths of the base + session.
194
                $session = api_get_session_condition($session_id);
195
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
196
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
197
198
                // Selecting by view_count descending allows to get the highest view_count first.
199
                $sql = "SELECT * FROM $lp_table
200
                        WHERE 
201
                            c_id = $course_id AND 
202
                            lp_id = $lp_id AND 
203
                            user_id = $user_id 
204
                            $session
205
                        ORDER BY view_count DESC";
206
                $res = Database::query($sql);
207
                if ($debug) {
208
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
209
                }
210
211
                if (Database::num_rows($res) > 0) {
212
                    if ($debug) {
213
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
214
                    }
215
                    $row = Database::fetch_array($res);
216
                    $this->attempt = $row['view_count'];
217
                    $this->lp_view_id = $row['id'];
218
                    $this->last_item_seen = $row['last_item'];
219
                    $this->progress_db = $row['progress'];
220
                    $this->lp_view_session_id = $row['session_id'];
221
                } elseif (!api_is_invitee()) {
222
                    if ($debug) {
223
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
224
                    }
225
                    $this->attempt = 1;
226
                    $params = [
227
                        'c_id' => $course_id,
228
                        'lp_id' => $lp_id,
229
                        'user_id' => $user_id,
230
                        'view_count' => 1,
231
                        'session_id' => $session_id,
232
                        'last_item' => 0,
233
                    ];
234
                    $this->last_item_seen = 0;
235
                    $this->lp_view_session_id = $session_id;
236
                    $this->lp_view_id = Database::insert($lp_table, $params);
237
                    if (!empty($this->lp_view_id)) {
238
                        $sql = "UPDATE $lp_table SET id = iid
239
                                WHERE iid = ".$this->lp_view_id;
240
                        Database::query($sql);
241
                    }
242
                }
243
244
                // Initialise items.
245
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
246
                $sql = "SELECT * FROM $lp_item_table
247
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
248
                        ORDER BY parent_item_id, display_order";
249
                $res = Database::query($sql);
250
251
                if ($debug) {
252
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
253
                    error_log('-- Start while--');
254
                }
255
256
                $lp_item_id_list = [];
257
                while ($row = Database::fetch_array($res)) {
258
                    $lp_item_id_list[] = $row['iid'];
259
                    switch ($this->type) {
260
                        case 3: //aicc
261
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
262
                            if (is_object($oItem)) {
263
                                $my_item_id = $oItem->get_id();
264
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
265
                                $oItem->set_prevent_reinit($this->prevent_reinit);
266
                                // Don't use reference here as the next loop will make the pointed object change.
267
                                $this->items[$my_item_id] = $oItem;
268
                                $this->refs_list[$oItem->ref] = $my_item_id;
269
                                if ($debug) {
270
                                    error_log(
271
                                        'learnpath::__construct() - '.
272
                                        'aicc object with id '.$my_item_id.
273
                                        ' set in items[]',
274
                                        0
275
                                    );
276
                                }
277
                            }
278
                            break;
279
                        case 2:
280
                            $oItem = new scormItem('db', $row['iid'], $course_id);
281
                            if (is_object($oItem)) {
282
                                $my_item_id = $oItem->get_id();
283
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
284
                                $oItem->set_prevent_reinit($this->prevent_reinit);
285
                                // Don't use reference here as the next loop will make the pointed object change.
286
                                $this->items[$my_item_id] = $oItem;
287
                                $this->refs_list[$oItem->ref] = $my_item_id;
288
                                if ($debug) {
289
                                    error_log('object with id '.$my_item_id.' set in items[]');
290
                                }
291
                            }
292
                            break;
293
                        case 1:
294
                        default:
295
                            if ($debug) {
296
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
297
                            }
298
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
299
300
                            if ($debug) {
301
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
302
                            }
303
                            if (is_object($oItem)) {
304
                                $my_item_id = $oItem->get_id();
305
                                // Moved down to when we are sure the item_view exists.
306
                                //$oItem->set_lp_view($this->lp_view_id);
307
                                $oItem->set_prevent_reinit($this->prevent_reinit);
308
                                // Don't use reference here as the next loop will make the pointed object change.
309
                                $this->items[$my_item_id] = $oItem;
310
                                $this->refs_list[$my_item_id] = $my_item_id;
311
                                if ($debug) {
312
                                    error_log(
313
                                        'learnpath::__construct() '.__LINE__.
314
                                        ' - object with id '.$my_item_id.' set in items[]'
315
                                    );
316
                                }
317
                            }
318
                            break;
319
                    }
320
321
                    // Setting the object level with variable $this->items[$i][parent]
322
                    foreach ($this->items as $itemLPObject) {
323
                        $level = self::get_level_for_item(
324
                            $this->items,
325
                            $itemLPObject->db_id
326
                        );
327
                        $itemLPObject->level = $level;
328
                    }
329
330
                    // Setting the view in the item object.
331
                    if (is_object($this->items[$row['iid']])) {
332
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
333
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
334
                            $this->items[$row['iid']]->current_start_time = 0;
335
                            $this->items[$row['iid']]->current_stop_time = 0;
336
                        }
337
                    }
338
                }
339
340
                if ($debug) {
341
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
342
                }
343
344
                if (!empty($lp_item_id_list)) {
345
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
346
                    if (!empty($lp_item_id_list_to_string)) {
347
                        // Get last viewing vars.
348
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
349
                        // This query should only return one or zero result.
350
                        $sql = "SELECT lp_item_id, status
351
                                FROM $itemViewTable
352
                                WHERE
353
                                    c_id = $course_id AND
354
                                    lp_view_id = ".$this->lp_view_id." AND
355
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
356
                                ORDER BY view_count DESC ";
357
358
                        if ($debug) {
359
                            error_log(
360
                                'learnpath::__construct() - Selecting item_views: '.$sql,
361
                                0
362
                            );
363
                        }
364
365
                        $status_list = [];
366
                        $res = Database::query($sql);
367
                        while ($row = Database:: fetch_array($res)) {
368
                            $status_list[$row['lp_item_id']] = $row['status'];
369
                        }
370
371
                        foreach ($lp_item_id_list as $item_id) {
372
                            if (isset($status_list[$item_id])) {
373
                                $status = $status_list[$item_id];
374
                                if (is_object($this->items[$item_id])) {
375
                                    $this->items[$item_id]->set_status($status);
376
                                    if (empty($status)) {
377
                                        $this->items[$item_id]->set_status(
378
                                            $this->default_status
379
                                        );
380
                                    }
381
                                }
382
                            } else {
383
                                if (!api_is_invitee()) {
384
                                    if (is_object($this->items[$item_id])) {
385
                                        $this->items[$item_id]->set_status(
386
                                            $this->default_status
387
                                        );
388
                                    }
389
390
                                    if (!empty($this->lp_view_id)) {
391
                                        // Add that row to the lp_item_view table so that
392
                                        // we have something to show in the stats page.
393
                                        $params = [
394
                                            'c_id' => $course_id,
395
                                            'lp_item_id' => $item_id,
396
                                            'lp_view_id' => $this->lp_view_id,
397
                                            'view_count' => 1,
398
                                            'status' => 'not attempted',
399
                                            'start_time' => time(),
400
                                            'total_time' => 0,
401
                                            'score' => 0,
402
                                        ];
403
                                        $insertId = Database::insert($itemViewTable, $params);
404
405
                                        if ($insertId) {
406
                                            $sql = "UPDATE $itemViewTable SET id = iid
407
                                                    WHERE iid = $insertId";
408
                                            Database::query($sql);
409
                                        }
410
411
                                        $this->items[$item_id]->set_lp_view(
412
                                            $this->lp_view_id,
413
                                            $course_id
414
                                        );
415
                                    }
416
                                }
417
                            }
418
                        }
419
                    }
420
                }
421
422
                $this->ordered_items = self::get_flat_ordered_items_list(
423
                    $this->get_id(),
424
                    0,
425
                    $course_id
426
                );
427
                $this->max_ordered_items = 0;
428
                foreach ($this->ordered_items as $index => $dummy) {
429
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
430
                        $this->max_ordered_items = $index;
431
                    }
432
                }
433
                // TODO: Define the current item better.
434
                $this->first();
435
                if ($debug) {
436
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
437
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
438
                }
439
            } else {
440
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
441
            }
442
        }
443
    }
444
445
    /**
446
     * @return string
447
     */
448
    public function getCourseCode()
449
    {
450
        return $this->cc;
451
    }
452
453
    /**
454
     * @return int
455
     */
456
    public function get_course_int_id()
457
    {
458
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
459
    }
460
461
    /**
462
     * @param $course_id
463
     *
464
     * @return int
465
     */
466
    public function set_course_int_id($course_id)
467
    {
468
        return $this->course_int_id = (int) $course_id;
469
    }
470
471
    /**
472
     * Function rewritten based on old_add_item() from Yannick Warnier.
473
     * Due the fact that users can decide where the item should come, I had to overlook this function and
474
     * I found it better to rewrite it. Old function is still available.
475
     * Added also the possibility to add a description.
476
     *
477
     * @param int    $parent
478
     * @param int    $previous
479
     * @param string $type
480
     * @param int    $id               resource ID (ref)
481
     * @param string $title
482
     * @param string $description
483
     * @param int    $prerequisites
484
     * @param int    $max_time_allowed
485
     * @param int    $userId
486
     *
487
     * @return int
488
     */
489
    public function add_item(
490
        $parent,
491
        $previous,
492
        $type = 'dir',
493
        $id,
494
        $title,
495
        $description,
496
        $prerequisites = 0,
497
        $max_time_allowed = 0,
498
        $userId = 0
499
    ) {
500
        $course_id = $this->course_info['real_id'];
501
        if ($this->debug > 0) {
502
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
503
        }
504
        if (empty($course_id)) {
505
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
506
            $this->course_info = api_get_course_info($this->cc);
507
            $course_id = $this->course_info['real_id'];
508
        }
509
        $userId = empty($userId) ? api_get_user_id() : $userId;
510
        $sessionId = api_get_session_id();
511
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
512
        $_course = $this->course_info;
513
        $parent = intval($parent);
514
        $previous = intval($previous);
515
        $id = intval($id);
516
        $max_time_allowed = htmlentities($max_time_allowed);
517
        if (empty($max_time_allowed)) {
518
            $max_time_allowed = 0;
519
        }
520
        $sql = "SELECT COUNT(iid) AS num
521
                FROM $tbl_lp_item
522
                WHERE
523
                    c_id = $course_id AND
524
                    lp_id = ".$this->get_id()." AND
525
                    parent_item_id = ".$parent;
526
527
        $res_count = Database::query($sql);
528
        $row = Database::fetch_array($res_count);
529
        $num = $row['num'];
530
531
        if ($num > 0) {
532
            if (empty($previous)) {
533
                $sql = "SELECT iid, next_item_id, display_order
534
                        FROM $tbl_lp_item
535
                        WHERE
536
                            c_id = $course_id AND
537
                            lp_id = ".$this->get_id()." AND
538
                            parent_item_id = $parent AND
539
                            previous_item_id = 0 OR
540
                            previous_item_id = $parent";
541
                $result = Database::query($sql);
542
                $row = Database::fetch_array($result);
543
                $tmp_previous = 0;
544
                $next = $row['iid'];
545
                $display_order = 0;
546
            } else {
547
                $previous = (int) $previous;
548
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
549
						FROM $tbl_lp_item
550
                        WHERE
551
                            c_id = $course_id AND
552
                            lp_id = ".$this->get_id()." AND
553
                            id = $previous";
554
                $result = Database::query($sql);
555
                $row = Database:: fetch_array($result);
556
                $tmp_previous = $row['iid'];
557
                $next = $row['next_item_id'];
558
                $display_order = $row['display_order'];
559
            }
560
        } else {
561
            $tmp_previous = 0;
562
            $next = 0;
563
            $display_order = 0;
564
        }
565
566
        $id = intval($id);
567
        $typeCleaned = Database::escape_string($type);
568
        $max_score = 100;
569
        if ($type == 'quiz') {
570
            $sql = 'SELECT SUM(ponderation)
571
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
572
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
573
                    ON
574
                        quiz_question.id = quiz_rel_question.question_id AND
575
                        quiz_question.c_id = quiz_rel_question.c_id
576
                    WHERE
577
                        quiz_rel_question.exercice_id = '.$id." AND
578
                        quiz_question.c_id = $course_id AND
579
                        quiz_rel_question.c_id = $course_id ";
580
            $rsQuiz = Database::query($sql);
581
            $max_score = Database::result($rsQuiz, 0, 0);
582
583
            // Disabling the exercise if we add it inside a LP
584
            $exercise = new Exercise($course_id);
585
            $exercise->read($id);
586
            $exercise->disable();
587
            $exercise->save();
588
        }
589
590
        $params = [
591
            "c_id" => $course_id,
592
            "lp_id" => $this->get_id(),
593
            "item_type" => $typeCleaned,
594
            "ref" => '',
595
            "title" => $title,
596
            "description" => $description,
597
            "path" => $id,
598
            "max_score" => $max_score,
599
            "parent_item_id" => $parent,
600
            "previous_item_id" => $previous,
601
            "next_item_id" => intval($next),
602
            "display_order" => $display_order + 1,
603
            "prerequisite" => $prerequisites,
604
            "max_time_allowed" => $max_time_allowed,
605
            'min_score' => 0,
606
            'launch_data' => '',
607
        ];
608
609
        if ($prerequisites != 0) {
610
            $params['prerequisite'] = $prerequisites;
611
        }
612
613
        $new_item_id = Database::insert($tbl_lp_item, $params);
614
615
        if ($this->debug > 2) {
616
            error_log('Inserting dir/chapter: '.$new_item_id, 0);
617
        }
618
619
        if ($new_item_id) {
620
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
621
            Database::query($sql);
622
623
            $sql = "UPDATE $tbl_lp_item
624
                    SET previous_item_id = $new_item_id 
625
                    WHERE c_id = $course_id AND id = $next";
626
            Database::query($sql);
627
628
            // Update the item that should be before the new item.
629
            $sql = "UPDATE $tbl_lp_item
630
                    SET next_item_id = $new_item_id
631
                    WHERE c_id = $course_id AND id = $tmp_previous";
632
            Database::query($sql);
633
634
            // Update all the items after the new item.
635
            $sql = "UPDATE $tbl_lp_item
636
                        SET display_order = display_order + 1
637
                    WHERE
638
                        c_id = $course_id AND
639
                        lp_id = ".$this->get_id()." AND
640
                        iid <> $new_item_id AND
641
                        parent_item_id = $parent AND
642
                        display_order > $display_order";
643
            Database::query($sql);
644
645
            // Update the item that should come after the new item.
646
            $sql = "UPDATE $tbl_lp_item
647
                    SET ref = $new_item_id
648
                    WHERE c_id = $course_id AND iid = $new_item_id";
649
            Database::query($sql);
650
651
            // Upload audio.
652
            if (!empty($_FILES['mp3']['name'])) {
653
                // Create the audio folder if it does not exist yet.
654
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
655
                if (!is_dir($filepath.'audio')) {
656
                    mkdir(
657
                        $filepath.'audio',
658
                        api_get_permissions_for_new_directories()
659
                    );
660
                    $audio_id = add_document(
661
                        $_course,
662
                        '/audio',
663
                        'folder',
664
                        0,
665
                        'audio',
666
                        '',
667
                        0,
668
                        true,
669
                        null,
670
                        $sessionId,
671
                        $userId
672
                    );
673
                    api_item_property_update(
674
                        $_course,
675
                        TOOL_DOCUMENT,
676
                        $audio_id,
677
                        'FolderCreated',
678
                        $userId,
679
                        null,
680
                        null,
681
                        null,
682
                        null,
683
                        $sessionId
684
                    );
685
                    api_item_property_update(
686
                        $_course,
687
                        TOOL_DOCUMENT,
688
                        $audio_id,
689
                        'invisible',
690
                        $userId,
691
                        null,
692
                        null,
693
                        null,
694
                        null,
695
                        $sessionId
696
                    );
697
                }
698
699
                $file_path = handle_uploaded_document(
700
                    $_course,
701
                    $_FILES['mp3'],
702
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
703
                    '/audio',
704
                    $userId,
705
                    '',
706
                    '',
707
                    '',
708
                    '',
709
                    false
710
                );
711
712
                // Getting the filename only.
713
                $file_components = explode('/', $file_path);
714
                $file = $file_components[count($file_components) - 1];
715
716
                // Store the mp3 file in the lp_item table.
717
                $sql = "UPDATE $tbl_lp_item SET
718
                          audio = '".Database::escape_string($file)."'
719
                        WHERE iid = '".intval($new_item_id)."'";
720
                Database::query($sql);
721
            }
722
        }
723
724
        return $new_item_id;
725
    }
726
727
    /**
728
     * Static admin function allowing addition of a learnpath to a course.
729
     *
730
     * @param string $courseCode
731
     * @param string $name
732
     * @param string $description
733
     * @param string $learnpath
734
     * @param string $origin
735
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
736
     * @param string $publicated_on
737
     * @param string $expired_on
738
     * @param int    $categoryId
739
     * @param int    $userId
740
     *
741
     * @return int The new learnpath ID on success, 0 on failure
742
     */
743
    public static function add_lp(
744
        $courseCode,
745
        $name,
746
        $description = '',
747
        $learnpath = 'guess',
748
        $origin = 'zip',
749
        $zipname = '',
750
        $publicated_on = '',
751
        $expired_on = '',
752
        $categoryId = 0,
753
        $userId = 0
754
    ) {
755
        global $charset;
756
757
        if (!empty($courseCode)) {
758
            $courseInfo = api_get_course_info($courseCode);
759
            $course_id = $courseInfo['real_id'];
760
        } else {
761
            $course_id = api_get_course_int_id();
762
            $courseInfo = api_get_course_info();
763
        }
764
765
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
766
        // Check course code exists.
767
        // Check lp_name doesn't exist, otherwise append something.
768
        $i = 0;
769
        $name = Database::escape_string($name);
770
        $categoryId = intval($categoryId);
771
772
        // Session id.
773
        $session_id = api_get_session_id();
774
        $userId = empty($userId) ? api_get_user_id() : $userId;
775
        $check_name = "SELECT * FROM $tbl_lp
776
                       WHERE c_id = $course_id AND name = '$name'";
777
778
        $res_name = Database::query($check_name);
779
780
        if (empty($publicated_on)) {
781
            $publicated_on = null;
782
        } else {
783
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
784
        }
785
786
        if (empty($expired_on)) {
787
            $expired_on = null;
788
        } else {
789
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
790
        }
791
792
        while (Database::num_rows($res_name)) {
793
            // There is already one such name, update the current one a bit.
794
            $i++;
795
            $name = $name.' - '.$i;
796
            $check_name = "SELECT * FROM $tbl_lp 
797
                           WHERE c_id = $course_id AND name = '$name'";
798
            $res_name = Database::query($check_name);
799
        }
800
        // New name does not exist yet; keep it.
801
        // Escape description.
802
        // Kevin: added htmlentities().
803
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
804
        $type = 1;
805
        switch ($learnpath) {
806
            case 'guess':
807
                break;
808
            case 'dokeos':
809
            case 'chamilo':
810
                $type = 1;
811
                break;
812
            case 'aicc':
813
                break;
814
        }
815
816
        switch ($origin) {
817
            case 'zip':
818
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
819
                break;
820
            case 'manual':
821
            default:
822
                $get_max = "SELECT MAX(display_order) 
823
                            FROM $tbl_lp WHERE c_id = $course_id";
824
                $res_max = Database::query($get_max);
825
                if (Database::num_rows($res_max) < 1) {
826
                    $dsp = 1;
827
                } else {
828
                    $row = Database::fetch_array($res_max);
829
                    $dsp = $row[0] + 1;
830
                }
831
832
                $params = [
833
                    'c_id' => $course_id,
834
                    'lp_type' => $type,
835
                    'name' => $name,
836
                    'description' => $description,
837
                    'path' => '',
838
                    'default_view_mod' => 'embedded',
839
                    'default_encoding' => 'UTF-8',
840
                    'display_order' => $dsp,
841
                    'content_maker' => 'Chamilo',
842
                    'content_local' => 'local',
843
                    'js_lib' => '',
844
                    'session_id' => $session_id,
845
                    'created_on' => api_get_utc_datetime(),
846
                    'modified_on' => api_get_utc_datetime(),
847
                    'publicated_on' => $publicated_on,
848
                    'expired_on' => $expired_on,
849
                    'category_id' => $categoryId,
850
                    'force_commit' => 0,
851
                    'content_license' => '',
852
                    'debug' => 0,
853
                    'theme' => '',
854
                    'preview_image' => '',
855
                    'author' => '',
856
                    'prerequisite' => 0,
857
                    'hide_toc_frame' => 0,
858
                    'seriousgame_mode' => 0,
859
                    'autolaunch' => 0,
860
                    'max_attempts' => 0,
861
                    'subscribe_users' => 0,
862
                    'accumulate_scorm_time' => 1,
863
                ];
864
                $id = Database::insert($tbl_lp, $params);
865
866
                if ($id > 0) {
867
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
868
                    Database::query($sql);
869
870
                    // Insert into item_property.
871
                    api_item_property_update(
872
                        $courseInfo,
873
                        TOOL_LEARNPATH,
874
                        $id,
875
                        'LearnpathAdded',
876
                        $userId
877
                    );
878
                    api_set_default_visibility(
879
                        $id,
880
                        TOOL_LEARNPATH,
881
                        0,
882
                        $courseInfo,
883
                        $session_id,
884
                        $userId
885
                    );
886
887
                    return $id;
888
                }
889
                break;
890
        }
891
    }
892
893
    /**
894
     * Auto completes the parents of an item in case it's been completed or passed.
895
     *
896
     * @param int $item Optional ID of the item from which to look for parents
897
     */
898
    public function autocomplete_parents($item)
899
    {
900
        $debug = $this->debug;
901
902
        if ($debug) {
903
            error_log('Learnpath::autocomplete_parents()');
904
        }
905
906
        if (empty($item)) {
907
            $item = $this->current;
908
        }
909
910
        $currentItem = $this->getItem($item);
911
        if ($currentItem) {
912
            $parent_id = $currentItem->get_parent();
913
            $parent = $this->getItem($parent_id);
914
            if ($parent) {
915
                // if $item points to an object and there is a parent.
916
                if ($debug) {
917
                    error_log(
918
                        'Autocompleting parent of item '.$item.' '.
919
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
920
                        0
921
                    );
922
                }
923
924
                // New experiment including failed and browsed in completed status.
925
                //$current_status = $currentItem->get_status();
926
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
927
                // Fixes chapter auto complete
928
                if (true) {
0 ignored issues
show
Bug introduced by
Avoid IF statements that are always true or false
Loading history...
929
                    // If the current item is completed or passes or succeeded.
930
                    $updateParentStatus = true;
931
                    if ($debug) {
932
                        error_log('Status of current item is alright');
933
                    }
934
935
                    foreach ($parent->get_children() as $childItemId) {
936
                        $childItem = $this->getItem($childItemId);
937
938
                        // If children was not set try to get the info
939
                        if (empty($childItem->db_item_view_id)) {
940
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
941
                        }
942
943
                        // Check all his brothers (parent's children) for completion status.
944
                        if ($childItemId != $item) {
945
                            if ($debug) {
946
                                error_log(
947
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
948
                                    0
949
                                );
950
                            }
951
                            // Trying completing parents of failed and browsed items as well.
952
                            if ($childItem->status_is(
953
                                [
954
                                    'completed',
955
                                    'passed',
956
                                    'succeeded',
957
                                    'browsed',
958
                                    'failed',
959
                                ]
960
                            )
961
                            ) {
962
                                // Keep completion status to true.
963
                                continue;
964
                            } else {
965
                                if ($debug > 2) {
966
                                    error_log(
967
                                        '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,
968
                                        0
969
                                    );
970
                                }
971
                                $updateParentStatus = false;
972
                                break;
973
                            }
974
                        }
975
                    }
976
977
                    if ($updateParentStatus) {
978
                        // If all the children were completed:
979
                        $parent->set_status('completed');
980
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
981
                        // Force the status to "completed"
982
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
983
                        $this->update_queue[$parent->get_id()] = 'completed';
984
                        if ($debug) {
985
                            error_log(
986
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
987
                                print_r($this->update_queue, 1),
988
                                0
989
                            );
990
                        }
991
                        // Recursive call.
992
                        $this->autocomplete_parents($parent->get_id());
993
                    }
994
                }
995
            } else {
996
                if ($debug) {
997
                    error_log("Parent #$parent_id does not exists");
998
                }
999
            }
1000
        } else {
1001
            if ($debug) {
1002
                error_log("#$item is an item that doesn't have parents");
1003
            }
1004
        }
1005
    }
1006
1007
    /**
1008
     * Closes the current resource.
1009
     *
1010
     * Stops the timer
1011
     * Saves into the database if required
1012
     * Clears the current resource data from this object
1013
     *
1014
     * @return bool True on success, false on failure
1015
     */
1016
    public function close()
1017
    {
1018
        if ($this->debug > 0) {
1019
            error_log('In learnpath::close()', 0);
1020
        }
1021
        if (empty($this->lp_id)) {
1022
            $this->error = 'Trying to close this learnpath but no ID is set';
1023
1024
            return false;
1025
        }
1026
        $this->current_time_stop = time();
1027
        $this->ordered_items = [];
1028
        $this->index = 0;
1029
        unset($this->lp_id);
1030
        //unset other stuff
1031
        return true;
1032
    }
1033
1034
    /**
1035
     * Static admin function allowing removal of a learnpath.
1036
     *
1037
     * @param array  $courseInfo
1038
     * @param int    $id         Learnpath ID
1039
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1040
     *
1041
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1042
     */
1043
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1044
    {
1045
        $course_id = api_get_course_int_id();
1046
        if (!empty($courseInfo)) {
1047
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1048
        }
1049
1050
        // TODO: Implement a way of getting this to work when the current object is not set.
1051
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1052
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1053
        if (!empty($id) && ($id != $this->lp_id)) {
1054
            return false;
1055
        }
1056
1057
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1058
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1059
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1060
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1061
1062
        // Delete lp item id.
1063
        foreach ($this->items as $id => $dummy) {
1064
            $sql = "DELETE FROM $lp_item_view
1065
                    WHERE c_id = $course_id AND lp_item_id = '".$id."'";
1066
            Database::query($sql);
1067
        }
1068
1069
        // Proposed by Christophe (nickname: clefevre)
1070
        $sql = "DELETE FROM $lp_item
1071
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1072
        Database::query($sql);
1073
1074
        $sql = "DELETE FROM $lp_view 
1075
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1076
        Database::query($sql);
1077
1078
        self::toggle_publish($this->lp_id, 'i');
1079
1080
        if ($this->type == 2 || $this->type == 3) {
1081
            // This is a scorm learning path, delete the files as well.
1082
            $sql = "SELECT path FROM $lp
1083
                    WHERE iid = ".$this->lp_id;
1084
            $res = Database::query($sql);
1085
            if (Database::num_rows($res) > 0) {
1086
                $row = Database::fetch_array($res);
1087
                $path = $row['path'];
1088
                $sql = "SELECT id FROM $lp
1089
                        WHERE 
1090
                            c_id = $course_id AND
1091
                            path = '$path' AND 
1092
                            iid != ".$this->lp_id;
1093
                $res = Database::query($sql);
1094
                if (Database::num_rows($res) > 0) {
1095
                    // Another learning path uses this directory, so don't delete it.
1096
                    if ($this->debug > 2) {
1097
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1098
                    }
1099
                } else {
1100
                    // No other LP uses that directory, delete it.
1101
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1102
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir; // The absolute system path for this course.
1103
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1104
                        if ($this->debug > 2) {
1105
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1106
                        }
1107
                        // Proposed by Christophe (clefevre).
1108
                        if (strcmp(substr($path, -2), "/.") == 0) {
1109
                            $path = substr($path, 0, -1); // Remove "." at the end.
1110
                        }
1111
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1112
                        rmdirr($course_scorm_dir.$path);
1113
                    }
1114
                }
1115
            }
1116
        }
1117
1118
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1119
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1120
        // Delete tools
1121
        $sql = "DELETE FROM $tbl_tool
1122
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1123
        Database::query($sql);
1124
1125
        $sql = "DELETE FROM $lp 
1126
                WHERE iid = ".$this->lp_id;
1127
        Database::query($sql);
1128
        // Updates the display order of all lps.
1129
        $this->update_display_order();
1130
1131
        api_item_property_update(
1132
            api_get_course_info(),
1133
            TOOL_LEARNPATH,
1134
            $this->lp_id,
1135
            'delete',
1136
            api_get_user_id()
1137
        );
1138
1139
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1140
            api_get_course_id(),
1141
            4,
1142
            $id,
1143
            api_get_session_id()
1144
        );
1145
1146
        if ($link_info !== false) {
1147
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1148
        }
1149
1150
        if (api_get_setting('search_enabled') == 'true') {
1151
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1152
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1153
        }
1154
    }
1155
1156
    /**
1157
     * Removes all the children of one item - dangerous!
1158
     *
1159
     * @param int $id Element ID of which children have to be removed
1160
     *
1161
     * @return int Total number of children removed
1162
     */
1163
    public function delete_children_items($id)
1164
    {
1165
        $course_id = $this->course_info['real_id'];
1166
        if ($this->debug > 0) {
1167
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1168
        }
1169
        $num = 0;
1170
        if (empty($id) || $id != strval(intval($id))) {
1171
            return false;
1172
        }
1173
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1174
        $sql = "SELECT * FROM $lp_item 
1175
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1176
        $res = Database::query($sql);
1177
        while ($row = Database::fetch_array($res)) {
1178
            $num += $this->delete_children_items($row['iid']);
1179
            $sql = "DELETE FROM $lp_item 
1180
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1181
            Database::query($sql);
1182
            $num++;
1183
        }
1184
1185
        return $num;
1186
    }
1187
1188
    /**
1189
     * Removes an item from the current learnpath.
1190
     *
1191
     * @param int $id     Elem ID (0 if first)
1192
     * @param int $remove Whether to remove the resource/data from the
1193
     *                    system or leave it (default: 'keep', others 'remove')
1194
     *
1195
     * @return int Number of elements moved
1196
     *
1197
     * @todo implement resource removal
1198
     */
1199
    public function delete_item($id, $remove = 'keep')
1200
    {
1201
        $course_id = api_get_course_int_id();
1202
        if ($this->debug > 0) {
1203
            error_log('In learnpath::delete_item()', 0);
1204
        }
1205
        // TODO: Implement the resource removal.
1206
        if (empty($id) || $id != strval(intval($id))) {
1207
            return false;
1208
        }
1209
        // First select item to get previous, next, and display order.
1210
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1211
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1212
        $res_sel = Database::query($sql_sel);
1213
        if (Database::num_rows($res_sel) < 1) {
1214
            return false;
1215
        }
1216
        $row = Database::fetch_array($res_sel);
1217
        $previous = $row['previous_item_id'];
1218
        $next = $row['next_item_id'];
1219
        $display = $row['display_order'];
1220
        $parent = $row['parent_item_id'];
1221
        $lp = $row['lp_id'];
1222
        // Delete children items.
1223
        $num = $this->delete_children_items($id);
1224
        if ($this->debug > 2) {
1225
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1226
        }
1227
        // Now delete the item.
1228
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1229
        if ($this->debug > 2) {
1230
            error_log('Deleting item: '.$sql_del, 0);
1231
        }
1232
        Database::query($sql_del);
1233
        // Now update surrounding items.
1234
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1235
                    WHERE iid = $previous";
1236
        Database::query($sql_upd);
1237
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1238
                    WHERE iid = $next";
1239
        Database::query($sql_upd);
1240
        // Now update all following items with new display order.
1241
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1242
                    WHERE 
1243
                        c_id = $course_id AND 
1244
                        lp_id = $lp AND 
1245
                        parent_item_id = $parent AND 
1246
                        display_order > $display";
1247
        Database::query($sql_all);
1248
1249
        //Removing prerequisites since the item will not longer exist
1250
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1251
                    WHERE c_id = $course_id AND prerequisite = $id";
1252
        Database::query($sql_all);
1253
1254
        // Remove from search engine if enabled.
1255
        if (api_get_setting('search_enabled') == 'true') {
1256
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1257
            $sql = 'SELECT * FROM %s 
1258
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1259
                    LIMIT 1';
1260
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1261
            $res = Database::query($sql);
1262
            if (Database::num_rows($res) > 0) {
1263
                $row2 = Database::fetch_array($res);
1264
                $di = new ChamiloIndexer();
1265
                $di->remove_document($row2['search_did']);
1266
            }
1267
            $sql = 'DELETE FROM %s 
1268
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1269
                    LIMIT 1';
1270
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1271
            Database::query($sql);
1272
        }
1273
    }
1274
1275
    /**
1276
     * Updates an item's content in place.
1277
     *
1278
     * @param int    $id               Element ID
1279
     * @param int    $parent           Parent item ID
1280
     * @param int    $previous         Previous item ID
1281
     * @param string $title            Item title
1282
     * @param string $description      Item description
1283
     * @param string $prerequisites    Prerequisites (optional)
1284
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1285
     * @param int    $max_time_allowed
1286
     * @param string $url
1287
     *
1288
     * @return bool True on success, false on error
1289
     */
1290
    public function edit_item(
1291
        $id,
1292
        $parent,
1293
        $previous,
1294
        $title,
1295
        $description,
1296
        $prerequisites = '0',
1297
        $audio = [],
1298
        $max_time_allowed = 0,
1299
        $url = ''
1300
    ) {
1301
        $course_id = api_get_course_int_id();
1302
        $_course = api_get_course_info();
1303
1304
        if ($this->debug > 0) {
1305
            error_log('In learnpath::edit_item()', 0);
1306
        }
1307
        if (empty($max_time_allowed)) {
1308
            $max_time_allowed = 0;
1309
        }
1310
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1311
            return false;
1312
        }
1313
1314
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1315
        $sql = "SELECT * FROM $tbl_lp_item 
1316
                WHERE iid = $id";
1317
        $res_select = Database::query($sql);
1318
        $row_select = Database::fetch_array($res_select);
1319
        $audio_update_sql = '';
1320
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1321
            // Create the audio folder if it does not exist yet.
1322
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1323
            if (!is_dir($filepath.'audio')) {
1324
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1325
                $audio_id = add_document(
1326
                    $_course,
1327
                    '/audio',
1328
                    'folder',
1329
                    0,
1330
                    'audio'
1331
                );
1332
                api_item_property_update(
1333
                    $_course,
1334
                    TOOL_DOCUMENT,
1335
                    $audio_id,
1336
                    'FolderCreated',
1337
                    api_get_user_id(),
1338
                    null,
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    api_get_session_id()
1343
                );
1344
                api_item_property_update(
1345
                    $_course,
1346
                    TOOL_DOCUMENT,
1347
                    $audio_id,
1348
                    'invisible',
1349
                    api_get_user_id(),
1350
                    null,
1351
                    null,
1352
                    null,
1353
                    null,
1354
                    api_get_session_id()
1355
                );
1356
            }
1357
1358
            // Upload file in documents.
1359
            $pi = pathinfo($audio['name']);
1360
            if ($pi['extension'] == 'mp3') {
1361
                $c_det = api_get_course_info($this->cc);
1362
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1363
                $path = handle_uploaded_document(
1364
                    $c_det,
1365
                    $audio,
1366
                    $bp,
1367
                    '/audio',
1368
                    api_get_user_id(),
1369
                    0,
1370
                    null,
1371
                    0,
1372
                    'rename',
1373
                    false,
1374
                    0
1375
                );
1376
                $path = substr($path, 7);
1377
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1378
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1379
            }
1380
        }
1381
1382
        $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false;
1383
        $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false;
1384
1385
        // TODO: htmlspecialchars to be checked for encoding related problems.
1386
        if ($same_parent && $same_previous) {
1387
            // Only update title and description.
1388
            $sql = "UPDATE $tbl_lp_item
1389
                    SET title = '".Database::escape_string($title)."',
1390
                        prerequisite = '".$prerequisites."',
1391
                        description = '".Database::escape_string($description)."'
1392
                        ".$audio_update_sql.",
1393
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1394
                    WHERE iid = $id";
1395
            Database::query($sql);
1396
        } else {
1397
            $old_parent = $row_select['parent_item_id'];
1398
            $old_previous = $row_select['previous_item_id'];
1399
            $old_next = $row_select['next_item_id'];
1400
            $old_order = $row_select['display_order'];
1401
            $old_prerequisite = $row_select['prerequisite'];
1402
            $old_max_time_allowed = $row_select['max_time_allowed'];
1403
1404
            /* BEGIN -- virtually remove the current item id */
1405
            /* for the next and previous item it is like the current item doesn't exist anymore */
1406
            if ($old_previous != 0) {
1407
                // Next
1408
                $sql = "UPDATE $tbl_lp_item
1409
                        SET next_item_id = $old_next
1410
                        WHERE iid = $old_previous";
1411
                Database::query($sql);
1412
            }
1413
1414
            if ($old_next != 0) {
1415
                // Previous
1416
                $sql = "UPDATE $tbl_lp_item
1417
                        SET previous_item_id = $old_previous
1418
                        WHERE iid = $old_next";
1419
                Database::query($sql);
1420
            }
1421
1422
            // display_order - 1 for every item with a display_order
1423
            // bigger then the display_order of the current item.
1424
            $sql = "UPDATE $tbl_lp_item
1425
                    SET display_order = display_order - 1
1426
                    WHERE
1427
                        c_id = $course_id AND
1428
                        display_order > $old_order AND
1429
                        lp_id = ".$this->lp_id." AND
1430
                        parent_item_id = $old_parent";
1431
            Database::query($sql);
1432
            /* END -- virtually remove the current item id */
1433
1434
            /* BEGIN -- update the current item id to his new location */
1435
            if ($previous == 0) {
1436
                // Select the data of the item that should come after the current item.
1437
                $sql = "SELECT id, display_order
1438
                        FROM $tbl_lp_item
1439
                        WHERE
1440
                            c_id = $course_id AND
1441
                            lp_id = ".$this->lp_id." AND
1442
                            parent_item_id = $parent AND
1443
                            previous_item_id = $previous";
1444
                $res_select_old = Database::query($sql);
1445
                $row_select_old = Database::fetch_array($res_select_old);
1446
1447
                // If the new parent didn't have children before.
1448
                if (Database::num_rows($res_select_old) == 0) {
1449
                    $new_next = 0;
1450
                    $new_order = 1;
1451
                } else {
1452
                    $new_next = $row_select_old['id'];
1453
                    $new_order = $row_select_old['display_order'];
1454
                }
1455
            } else {
1456
                // Select the data of the item that should come before the current item.
1457
                $sql = "SELECT next_item_id, display_order
1458
                        FROM $tbl_lp_item
1459
                        WHERE iid = $previous";
1460
                $res_select_old = Database::query($sql);
1461
                $row_select_old = Database::fetch_array($res_select_old);
1462
                $new_next = $row_select_old['next_item_id'];
1463
                $new_order = $row_select_old['display_order'] + 1;
1464
            }
1465
1466
            // TODO: htmlspecialchars to be checked for encoding related problems.
1467
            // Update the current item with the new data.
1468
            $sql = "UPDATE $tbl_lp_item
1469
                    SET
1470
                        title = '".Database::escape_string($title)."',
1471
                        description = '".Database::escape_string($description)."',
1472
                        parent_item_id = $parent,
1473
                        previous_item_id = $previous,
1474
                        next_item_id = $new_next,
1475
                        display_order = $new_order
1476
                        $audio_update_sql
1477
                    WHERE iid = $id";
1478
            Database::query($sql);
1479
1480
            if ($previous != 0) {
1481
                // Update the previous item's next_item_id.
1482
                $sql = "UPDATE $tbl_lp_item
1483
                        SET next_item_id = $id
1484
                        WHERE iid = $previous";
1485
                Database::query($sql);
1486
            }
1487
1488
            if ($new_next != 0) {
1489
                // Update the next item's previous_item_id.
1490
                $sql = "UPDATE $tbl_lp_item
1491
                        SET previous_item_id = $id
1492
                        WHERE iid = $new_next";
1493
                Database::query($sql);
1494
            }
1495
1496
            if ($old_prerequisite != $prerequisites) {
1497
                $sql = "UPDATE $tbl_lp_item
1498
                        SET prerequisite = '$prerequisites'
1499
                        WHERE iid = $id";
1500
                Database::query($sql);
1501
            }
1502
1503
            if ($old_max_time_allowed != $max_time_allowed) {
1504
                // update max time allowed
1505
                $sql = "UPDATE $tbl_lp_item
1506
                        SET max_time_allowed = $max_time_allowed
1507
                        WHERE iid = $id";
1508
                Database::query($sql);
1509
            }
1510
1511
            // Update all the items with the same or a bigger display_order than the current item.
1512
            $sql = "UPDATE $tbl_lp_item
1513
                    SET display_order = display_order + 1
1514
                    WHERE
1515
                       c_id = $course_id AND
1516
                       lp_id = ".$this->get_id()." AND
1517
                       iid <> $id AND
1518
                       parent_item_id = $parent AND
1519
                       display_order >= $new_order";
1520
            Database::query($sql);
1521
        }
1522
1523
        if ($row_select['item_type'] == 'link') {
1524
            $link = new Link();
1525
            $linkId = $row_select['path'];
1526
            $link->updateLink($linkId, $url);
1527
        }
1528
    }
1529
1530
    /**
1531
     * Updates an item's prereq in place.
1532
     *
1533
     * @param int    $id              Element ID
1534
     * @param string $prerequisite_id Prerequisite Element ID
1535
     * @param int    $mastery_score   Prerequisite min score
1536
     * @param int    $max_score       Prerequisite max score
1537
     *
1538
     * @return bool True on success, false on error
1539
     */
1540
    public function edit_item_prereq(
1541
        $id,
1542
        $prerequisite_id,
1543
        $mastery_score = 0,
1544
        $max_score = 100
1545
    ) {
1546
        $course_id = api_get_course_int_id();
1547
        if ($this->debug > 0) {
1548
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1549
        }
1550
1551
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1552
            return false;
1553
        }
1554
1555
        $prerequisite_id = intval($prerequisite_id);
1556
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1557
1558
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1559
            $mastery_score = 0;
1560
        }
1561
1562
        if (!is_numeric($max_score) || $max_score < 0) {
1563
            $max_score = 100;
1564
        }
1565
1566
        /*if ($mastery_score > $max_score) {
1567
            $max_score = $mastery_score;
1568
        }*/
1569
1570
        if (!is_numeric($prerequisite_id)) {
1571
            $prerequisite_id = 'NULL';
1572
        }
1573
1574
        $mastery_score = floatval($mastery_score);
1575
        $max_score = floatval($max_score);
1576
1577
        $sql = " UPDATE $tbl_lp_item
1578
                 SET
1579
                    prerequisite = $prerequisite_id ,
1580
                    prerequisite_min_score = $mastery_score ,
1581
                    prerequisite_max_score = $max_score
1582
                 WHERE iid = $id";
1583
        Database::query($sql);
1584
        // TODO: Update the item object (can be ignored for now because refreshed).
1585
        return true;
1586
    }
1587
1588
    /**
1589
     * Gets all the chapters belonging to the same parent as the item/chapter given
1590
     * Can also be called as abstract method.
1591
     *
1592
     * @param int $id Item ID
1593
     *
1594
     * @return array A list of all the "brother items" (or an empty array on failure)
1595
     */
1596
    public function getSiblingDirectories($id)
1597
    {
1598
        $course_id = api_get_course_int_id();
1599
        if ($this->debug > 0) {
1600
            error_log('In learnpath::getSiblingDirectories()', 0);
1601
        }
1602
1603
        if (empty($id) || $id != strval(intval($id))) {
1604
            return [];
1605
        }
1606
1607
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1608
        $sql_parent = "SELECT * FROM $lp_item
1609
                       WHERE iid = $id AND item_type='dir'";
1610
        $res_parent = Database::query($sql_parent);
1611
        if (Database::num_rows($res_parent) > 0) {
1612
            $row_parent = Database::fetch_array($res_parent);
1613
            $parent = $row_parent['parent_item_id'];
1614
            $sql = "SELECT * FROM $lp_item
1615
                    WHERE
1616
                        parent_item_id = $parent AND
1617
                        iid = $id AND
1618
                        item_type='dir'
1619
                    ORDER BY display_order";
1620
            $res_bros = Database::query($sql);
1621
1622
            $list = [];
1623
            while ($row_bro = Database::fetch_array($res_bros)) {
1624
                $list[] = $row_bro;
1625
            }
1626
1627
            return $list;
1628
        }
1629
1630
        return [];
1631
    }
1632
1633
    /**
1634
     * Gets all the items belonging to the same parent as the item given
1635
     * Can also be called as abstract method.
1636
     *
1637
     * @param int $id Item ID
1638
     *
1639
     * @return array A list of all the "brother items" (or an empty array on failure)
1640
     */
1641
    public function get_brother_items($id)
1642
    {
1643
        $course_id = api_get_course_int_id();
1644
        if ($this->debug > 0) {
1645
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1646
        }
1647
1648
        if (empty($id) || $id != strval(intval($id))) {
1649
            return [];
1650
        }
1651
1652
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1653
        $sql_parent = "SELECT * FROM $lp_item 
1654
                       WHERE iid = $id";
1655
        $res_parent = Database::query($sql_parent);
1656
        if (Database::num_rows($res_parent) > 0) {
1657
            $row_parent = Database::fetch_array($res_parent);
1658
            $parent = $row_parent['parent_item_id'];
1659
            $sql = "SELECT * FROM $lp_item 
1660
                    WHERE c_id = $course_id AND parent_item_id = $parent
1661
                    ORDER BY display_order";
1662
            $res_bros = Database::query($sql);
1663
            $list = [];
1664
            while ($row_bro = Database::fetch_array($res_bros)) {
1665
                $list[] = $row_bro;
1666
            }
1667
1668
            return $list;
1669
        }
1670
1671
        return [];
1672
    }
1673
1674
    /**
1675
     * Get the specific prefix index terms of this learning path.
1676
     *
1677
     * @param string $prefix
1678
     *
1679
     * @return array Array of terms
1680
     */
1681
    public function get_common_index_terms_by_prefix($prefix)
1682
    {
1683
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1684
        $terms = get_specific_field_values_list_by_prefix(
1685
            $prefix,
1686
            $this->cc,
1687
            TOOL_LEARNPATH,
1688
            $this->lp_id
1689
        );
1690
        $prefix_terms = [];
1691
        if (!empty($terms)) {
1692
            foreach ($terms as $term) {
1693
                $prefix_terms[] = $term['value'];
1694
            }
1695
        }
1696
1697
        return $prefix_terms;
1698
    }
1699
1700
    /**
1701
     * Gets the number of items currently completed.
1702
     *
1703
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1704
     *
1705
     * @return int The number of items currently completed
1706
     */
1707
    public function get_complete_items_count($failedStatusException = false)
1708
    {
1709
        if ($this->debug > 0) {
1710
            error_log('In learnpath::get_complete_items_count()', 0);
1711
        }
1712
        $i = 0;
1713
        $completedStatusList = [
1714
            'completed',
1715
            'passed',
1716
            'succeeded',
1717
            'browsed',
1718
        ];
1719
1720
        if (!$failedStatusException) {
1721
            $completedStatusList[] = 'failed';
1722
        }
1723
1724
        foreach ($this->items as $id => $dummy) {
1725
            // Trying failed and browsed considered "progressed" as well.
1726
            if ($this->items[$id]->status_is($completedStatusList) &&
1727
                $this->items[$id]->get_type() != 'dir'
1728
            ) {
1729
                $i++;
1730
            }
1731
        }
1732
1733
        return $i;
1734
    }
1735
1736
    /**
1737
     * Gets the current item ID.
1738
     *
1739
     * @return int The current learnpath item id
1740
     */
1741
    public function get_current_item_id()
1742
    {
1743
        $current = 0;
1744
        if ($this->debug > 0) {
1745
            error_log('In learnpath::get_current_item_id()', 0);
1746
        }
1747
        if (!empty($this->current)) {
1748
            $current = $this->current;
1749
        }
1750
        if ($this->debug > 2) {
1751
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1752
        }
1753
1754
        return $current;
1755
    }
1756
1757
    /**
1758
     * Force to get the first learnpath item id.
1759
     *
1760
     * @return int The current learnpath item id
1761
     */
1762
    public function get_first_item_id()
1763
    {
1764
        $current = 0;
1765
        if (is_array($this->ordered_items)) {
1766
            $current = $this->ordered_items[0];
1767
        }
1768
1769
        return $current;
1770
    }
1771
1772
    /**
1773
     * Gets the total number of items available for viewing in this SCORM.
1774
     *
1775
     * @return int The total number of items
1776
     */
1777
    public function get_total_items_count()
1778
    {
1779
        if ($this->debug > 0) {
1780
            error_log('In learnpath::get_total_items_count()', 0);
1781
        }
1782
1783
        return count($this->items);
1784
    }
1785
1786
    /**
1787
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1788
     *
1789
     * @return int The total no-chapters number of items
1790
     */
1791
    public function getTotalItemsCountWithoutDirs()
1792
    {
1793
        if ($this->debug > 0) {
1794
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1795
        }
1796
        $total = 0;
1797
        $typeListNotToCount = self::getChapterTypes();
1798
        foreach ($this->items as $temp2) {
1799
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1800
                $total++;
1801
            }
1802
        }
1803
1804
        return $total;
1805
    }
1806
1807
    /**
1808
     *  Sets the first element URL.
1809
     */
1810
    public function first()
1811
    {
1812
        if ($this->debug > 0) {
1813
            error_log('In learnpath::first()', 0);
1814
            error_log('$this->last_item_seen '.$this->last_item_seen);
1815
        }
1816
1817
        // Test if the last_item_seen exists and is not a dir.
1818
        if (count($this->ordered_items) == 0) {
1819
            $this->index = 0;
1820
        }
1821
1822
        if (!empty($this->last_item_seen) &&
1823
            !empty($this->items[$this->last_item_seen]) &&
1824
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1825
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1826
            //&& !$this->items[$this->last_item_seen]->is_done()
1827
        ) {
1828
            if ($this->debug > 2) {
1829
                error_log('In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.$this->items[$this->last_item_seen]->get_type(), 0);
1830
            }
1831
            $index = -1;
1832
            foreach ($this->ordered_items as $myindex => $item_id) {
1833
                if ($item_id == $this->last_item_seen) {
1834
                    $index = $myindex;
1835
                    break;
1836
                }
1837
            }
1838
            if ($index == -1) {
1839
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1840
                if ($this->debug > 2) {
1841
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1842
                }
1843
1844
                return false;
1845
            } else {
1846
                $this->last = $this->last_item_seen;
1847
                $this->current = $this->last_item_seen;
1848
                $this->index = $index;
1849
            }
1850
        } else {
1851
            if ($this->debug > 2) {
1852
                error_log('In learnpath::first() - No last item seen', 0);
1853
            }
1854
            $index = 0;
1855
            // Loop through all ordered items and stop at the first item that is
1856
            // not a directory *and* that has not been completed yet.
1857
            while (!empty($this->ordered_items[$index]) &&
1858
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1859
                (
1860
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1861
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1862
                ) && $index < $this->max_ordered_items) {
1863
                $index++;
1864
            }
1865
1866
            $this->last = $this->current;
1867
            // current is
1868
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1869
            $this->index = $index;
1870
            if ($this->debug > 2) {
1871
                error_log('$index '.$index);
1872
                error_log('In learnpath::first() - No last item seen');
1873
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1874
            }
1875
        }
1876
        if ($this->debug > 2) {
1877
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1878
        }
1879
    }
1880
1881
    /**
1882
     * Gets the information about an item in a format usable as JavaScript to update
1883
     * the JS API by just printing this content into the <head> section of the message frame.
1884
     *
1885
     * @param int $item_id
1886
     *
1887
     * @return string
1888
     */
1889
    public function get_js_info($item_id = 0)
1890
    {
1891
        if ($this->debug > 0) {
1892
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1893
        }
1894
1895
        $info = '';
1896
        $item_id = intval($item_id);
1897
1898
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1899
            //if item is defined, return values from DB
1900
            $oItem = $this->items[$item_id];
1901
            $info .= '<script language="javascript">';
1902
            $info .= "top.set_score(".$oItem->get_score().");\n";
1903
            $info .= "top.set_max(".$oItem->get_max().");\n";
1904
            $info .= "top.set_min(".$oItem->get_min().");\n";
1905
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1906
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1907
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1908
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1909
            $info .= "top.set_flag_synchronized();";
1910
            $info .= '</script>';
1911
            if ($this->debug > 2) {
1912
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1913
            }
1914
1915
            return $info;
1916
        } else {
1917
            // If item_id is empty, just update to default SCORM data.
1918
            $info .= '<script language="javascript">';
1919
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1920
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1921
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1922
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1923
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1924
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1925
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1926
            $info .= "top.set_flag_synchronized();";
1927
            $info .= '</script>';
1928
            if ($this->debug > 2) {
1929
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1930
            }
1931
1932
            return $info;
1933
        }
1934
    }
1935
1936
    /**
1937
     * Gets the js library from the database.
1938
     *
1939
     * @return string The name of the javascript library to be used
1940
     */
1941
    public function get_js_lib()
1942
    {
1943
        $lib = '';
1944
        if (!empty($this->js_lib)) {
1945
            $lib = $this->js_lib;
1946
        }
1947
1948
        return $lib;
1949
    }
1950
1951
    /**
1952
     * Gets the learnpath database ID.
1953
     *
1954
     * @return int Learnpath ID in the lp table
1955
     */
1956
    public function get_id()
1957
    {
1958
        if (!empty($this->lp_id)) {
1959
            return $this->lp_id;
1960
        } else {
1961
            return 0;
1962
        }
1963
    }
1964
1965
    /**
1966
     * Gets the last element URL.
1967
     *
1968
     * @return string URL to load into the viewer
1969
     */
1970
    public function get_last()
1971
    {
1972
        if ($this->debug > 0) {
1973
            error_log('In learnpath::get_last()', 0);
1974
        }
1975
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1976
        if (count($this->ordered_items) > 0) {
1977
            $this->index = count($this->ordered_items) - 1;
1978
1979
            return $this->ordered_items[$this->index];
1980
        }
1981
1982
        return false;
1983
    }
1984
1985
    /**
1986
     * Gets the navigation bar for the learnpath display screen.
1987
     *
1988
     * @return string The HTML string to use as a navigation bar
1989
     */
1990
    public function get_navigation_bar($idBar = null, $display = null)
1991
    {
1992
        if ($this->debug > 0) {
1993
            error_log('In learnpath::get_navigation_bar()', 0);
1994
        }
1995
        if (empty($idBar)) {
1996
            $idBar = 'control-top';
1997
        }
1998
        $lpId = $this->lp_id;
1999
        $mycurrentitemid = $this->get_current_item_id();
2000
2001
        $reportingText = get_lang('Reporting');
2002
        $previousText = get_lang('ScormPrevious');
2003
        $nextText = get_lang('ScormNext');
2004
        $fullScreenText = get_lang('ScormExitFullScreen');
2005
2006
        $settings = api_get_configuration_value('lp_view_settings');
2007
        $display = isset($settings['display']) ? $settings['display'] : false;
2008
        $reportingIcon = '
2009
            <a class="icon-toolbar" 
2010
                id="stats_link"
2011
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2012
                onclick="window.parent.API.save_asset(); return true;" 
2013
                target="content_name" title="'.$reportingText.'">
2014
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2015
            </a>';
2016
2017
        if (!empty($display)) {
2018
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2019
            if ($showReporting == false) {
2020
                $reportingIcon = '';
2021
            }
2022
        }
2023
2024
        $previousIcon = '
2025
            <a class="icon-toolbar" id="scorm-previous" href="#" 
2026
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2027
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2028
            </a>';
2029
2030
        $nextIcon = '
2031
            <a class="icon-toolbar" id="scorm-next" href="#" 
2032
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2033
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2034
            </a>';
2035
2036
        if ($this->mode == 'fullscreen') {
2037
            $navbar = '
2038
                  <span id="'.$idBar.'" class="buttons">
2039
                    '.$reportingIcon.'
2040
                    '.$previousIcon.'                    
2041
                    '.$nextIcon.'
2042
                    <a class="icon-toolbar" id="view-embedded" 
2043
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2044
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2045
                    </a>
2046
                  </span>';
2047
        } else {
2048
            $navbar = '
2049
            <span id="'.$idBar.'" class="buttons text-right">
2050
                '.$reportingIcon.'
2051
                '.$previousIcon.'
2052
                '.$nextIcon.'               
2053
            </span>';
2054
        }
2055
2056
        return $navbar;
2057
    }
2058
2059
    /**
2060
     * Gets the next resource in queue (url).
2061
     *
2062
     * @return string URL to load into the viewer
2063
     */
2064
    public function get_next_index()
2065
    {
2066
        if ($this->debug > 0) {
2067
            error_log('In learnpath::get_next_index()', 0);
2068
        }
2069
        // TODO
2070
        $index = $this->index;
2071
        $index++;
2072
        if ($this->debug > 2) {
2073
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2074
        }
2075
        while (
2076
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2077
            $index < $this->max_ordered_items
2078
        ) {
2079
            $index++;
2080
            if ($index == $this->max_ordered_items) {
2081
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2082
                    return $this->index;
2083
                } else {
2084
                    return $index;
2085
                }
2086
            }
2087
        }
2088
        if (empty($this->ordered_items[$index])) {
2089
            return $this->index;
2090
        }
2091
        if ($this->debug > 2) {
2092
            error_log('index is now '.$index, 0);
2093
        }
2094
2095
        return $index;
2096
    }
2097
2098
    /**
2099
     * Gets item_id for the next element.
2100
     *
2101
     * @return int Next item (DB) ID
2102
     */
2103
    public function get_next_item_id()
2104
    {
2105
        if ($this->debug > 0) {
2106
            error_log('In learnpath::get_next_item_id()', 0);
2107
        }
2108
        $new_index = $this->get_next_index();
2109
        if (!empty($new_index)) {
2110
            if (isset($this->ordered_items[$new_index])) {
2111
                if ($this->debug > 2) {
2112
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2113
                }
2114
2115
                return $this->ordered_items[$new_index];
2116
            }
2117
        }
2118
        if ($this->debug > 2) {
2119
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2120
        }
2121
2122
        return 0;
2123
    }
2124
2125
    /**
2126
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2127
     *
2128
     * Generally, the package provided is in the form of a zip file, so the function
2129
     * has been written to test a zip file. If not a zip, the function will return the
2130
     * default return value: ''
2131
     *
2132
     * @param string $file_path the path to the file
2133
     * @param string $file_name the original name of the file
2134
     *
2135
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2136
     */
2137
    public static function get_package_type($file_path, $file_name)
2138
    {
2139
        // Get name of the zip file without the extension.
2140
        $file_info = pathinfo($file_name);
2141
        $extension = $file_info['extension']; // Extension only.
2142
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2143
                'dll',
2144
                'exe',
2145
            ])) {
2146
            return 'oogie';
2147
        }
2148
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2149
                'dll',
2150
                'exe',
2151
            ])) {
2152
            return 'woogie';
2153
        }
2154
2155
        $zipFile = new PclZip($file_path);
2156
        // Check the zip content (real size and file extension).
2157
        $zipContentArray = $zipFile->listContent();
2158
        $package_type = '';
2159
        $manifest = '';
2160
        $aicc_match_crs = 0;
2161
        $aicc_match_au = 0;
2162
        $aicc_match_des = 0;
2163
        $aicc_match_cst = 0;
2164
2165
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2166
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2167
            foreach ($zipContentArray as $thisContent) {
2168
                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...
2169
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2170
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2171
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2172
                    $package_type = 'scorm';
2173
                    break; // Exit the foreach loop.
2174
                } elseif (
2175
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2176
                    in_array(
2177
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2178
                        ['crs', 'au', 'des', 'cst']
2179
                    )
2180
                ) {
2181
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2182
                    switch ($ext) {
2183
                        case 'crs':
2184
                            $aicc_match_crs = 1;
2185
                            break;
2186
                        case 'au':
2187
                            $aicc_match_au = 1;
2188
                            break;
2189
                        case 'des':
2190
                            $aicc_match_des = 1;
2191
                            break;
2192
                        case 'cst':
2193
                            $aicc_match_cst = 1;
2194
                            break;
2195
                        default:
2196
                            break;
2197
                    }
2198
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2199
                } else {
2200
                    $package_type = '';
2201
                }
2202
            }
2203
        }
2204
2205
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2206
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2207
            $package_type = 'aicc';
2208
        }
2209
2210
        // Try with chamilo course builder
2211
        if (empty($package_type)) {
2212
            $package_type = 'chamilo';
2213
        }
2214
2215
        return $package_type;
2216
    }
2217
2218
    /**
2219
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2220
     *
2221
     * @return string URL to load into the viewer
2222
     */
2223
    public function get_previous_index()
2224
    {
2225
        if ($this->debug > 0) {
2226
            error_log('In learnpath::get_previous_index()', 0);
2227
        }
2228
        $index = $this->index;
2229
        if (isset($this->ordered_items[$index - 1])) {
2230
            $index--;
2231
            while (isset($this->ordered_items[$index]) &&
2232
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2233
            ) {
2234
                $index--;
2235
                if ($index < 0) {
2236
                    return $this->index;
2237
                }
2238
            }
2239
        } else {
2240
            if ($this->debug > 2) {
2241
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2242
            }
2243
            // There is no previous item.
2244
        }
2245
2246
        return $index;
2247
    }
2248
2249
    /**
2250
     * Gets item_id for the next element.
2251
     *
2252
     * @return int Previous item (DB) ID
2253
     */
2254
    public function get_previous_item_id()
2255
    {
2256
        if ($this->debug > 0) {
2257
            error_log('In learnpath::get_previous_item_id()', 0);
2258
        }
2259
        $new_index = $this->get_previous_index();
2260
2261
        return $this->ordered_items[$new_index];
2262
    }
2263
2264
    /**
2265
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2266
     *
2267
     * @param int    $lpItemId
2268
     * @param string $autostart
2269
     *
2270
     * @return string The mediaplayer HTML
2271
     */
2272
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2273
    {
2274
        $course_id = api_get_course_int_id();
2275
        $_course = api_get_course_info();
2276
        if (empty($_course)) {
2277
            return '';
2278
        }
2279
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2280
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2281
        $lpItemId = (int) $lpItemId;
2282
2283
        // Getting all the information about the item.
2284
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2285
                INNER JOIN $tbl_lp_item_view as lp_view
2286
                ON (lpi.iid = lp_view.lp_item_id)
2287
                WHERE
2288
                    lpi.iid = $lpItemId AND
2289
                    lp_view.c_id = $course_id";
2290
        $result = Database::query($sql);
2291
        $row = Database::fetch_assoc($result);
2292
        $output = '';
2293
2294
        if (!empty($row['audio'])) {
2295
            $list = $_SESSION['oLP']->get_toc();
2296
            $type_quiz = false;
2297
2298
            foreach ($list as $toc) {
2299
                if ($toc['id'] == $_SESSION['oLP']->current && $toc['type'] == 'quiz') {
2300
                    $type_quiz = true;
2301
                }
2302
            }
2303
2304
            if ($type_quiz) {
2305
                if ($_SESSION['oLP']->prevent_reinit == 1) {
2306
                    $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2307
                } else {
2308
                    $autostart_audio = $autostart;
2309
                }
2310
            } else {
2311
                $autostart_audio = 'true';
2312
            }
2313
2314
            $courseInfo = api_get_course_info();
2315
            $audio = $row['audio'];
2316
2317
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2318
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2319
2320
            if (!file_exists($file)) {
2321
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2322
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2323
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2324
            }
2325
2326
            $player = Display::getMediaPlayer(
2327
                $file,
2328
                [
2329
                    'id' => 'lp_audio_media_player',
2330
                    'url' => $url,
2331
                    'autoplay' => $autostart_audio,
2332
                    'width' => '100%',
2333
                ]
2334
            );
2335
2336
            // The mp3 player.
2337
            $output = '<div id="container">';
2338
            $output .= $player;
2339
            $output .= '</div>';
2340
        }
2341
2342
        return $output;
2343
    }
2344
2345
    /**
2346
     * @param int   $studentId
2347
     * @param int   $prerequisite
2348
     * @param array $courseInfo
2349
     * @param int   $sessionId
2350
     *
2351
     * @return bool
2352
     */
2353
    public static function isBlockedByPrerequisite(
2354
        $studentId,
2355
        $prerequisite,
2356
        $courseInfo,
2357
        $sessionId
2358
    ) {
2359
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2360
        if ($allow) {
2361
            if (api_is_allowed_to_edit() ||
2362
                api_is_platform_admin() ||
2363
                api_is_drh() ||
2364
                api_is_coach($sessionId, $courseInfo['real_id'])
2365
            ) {
2366
                return false;
2367
            }
2368
        }
2369
2370
        $isBlocked = false;
2371
2372
        if (!empty($prerequisite)) {
2373
            $progress = self::getProgress(
2374
                $prerequisite,
2375
                $studentId,
2376
                $courseInfo['real_id'],
2377
                $sessionId
2378
            );
2379
            if ($progress < 100) {
2380
                $isBlocked = true;
2381
            }
2382
        }
2383
2384
        return $isBlocked;
2385
    }
2386
2387
    /**
2388
     * Checks if the learning path is visible for student after the progress
2389
     * of its prerequisite is completed, considering the time availability and
2390
     * the LP visibility.
2391
     *
2392
     * @param int  $lp_id
2393
     * @param int  $student_id
2394
     * @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...
2395
     * @param int  $sessionId
2396
     *
2397
     * @return bool
2398
     */
2399
    public static function is_lp_visible_for_student(
2400
        $lp_id,
2401
        $student_id,
2402
        $courseCode = null,
2403
        $sessionId = 0
2404
    ) {
2405
        $courseInfo = api_get_course_info($courseCode);
2406
        $lp_id = (int) $lp_id;
2407
        $sessionId = (int) $sessionId;
2408
2409
        if (empty($courseInfo)) {
2410
            return false;
2411
        }
2412
2413
        if (empty($sessionId)) {
2414
            $sessionId = api_get_session_id();
2415
        }
2416
2417
        $itemInfo = api_get_item_property_info(
2418
            $courseInfo['real_id'],
2419
            TOOL_LEARNPATH,
2420
            $lp_id,
2421
            $sessionId
2422
        );
2423
2424
        // If the item was deleted.
2425
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2426
            return false;
2427
        }
2428
2429
        // @todo remove this query and load the row info as a parameter
2430
        $table = Database::get_course_table(TABLE_LP_MAIN);
2431
        // Get current prerequisite
2432
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2433
                FROM $table
2434
                WHERE iid = $lp_id";
2435
        $rs = Database::query($sql);
2436
        $now = time();
2437
        if (Database::num_rows($rs) > 0) {
2438
            $row = Database::fetch_array($rs, 'ASSOC');
2439
            $prerequisite = $row['prerequisite'];
2440
            $is_visible = true;
2441
2442
            $isBlocked = self::isBlockedByPrerequisite(
2443
                $student_id,
2444
                $prerequisite,
2445
                $courseInfo,
2446
                $sessionId
2447
            );
2448
2449
            if ($isBlocked) {
2450
                $is_visible = false;
2451
            }
2452
2453
            // Also check the time availability of the LP
2454
            if ($is_visible) {
2455
                // Adding visibility restrictions
2456
                if (!empty($row['publicated_on'])) {
2457
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2458
                        $is_visible = false;
2459
                    }
2460
                }
2461
                // Blocking empty start times see BT#2800
2462
                global $_custom;
2463
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2464
                    $_custom['lps_hidden_when_no_start_date']
2465
                ) {
2466
                    if (empty($row['publicated_on'])) {
2467
                        $is_visible = false;
2468
                    }
2469
                }
2470
2471
                if (!empty($row['expired_on'])) {
2472
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2473
                        $is_visible = false;
2474
                    }
2475
                }
2476
            }
2477
2478
            $subscriptionSettings = learnpath::getSubscriptionSettings();
2479
2480
            // Check if the subscription users/group to a LP is ON
2481
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2482
                $subscriptionSettings['allow_add_users_to_lp'] === true
2483
            ) {
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
     */
2536
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2537
    {
2538
        $lpId = (int) $lpId;
2539
        $userId = (int) $userId;
2540
        $courseId = (int) $courseId;
2541
        $sessionId = (int) $sessionId;
2542
        $progress = 0;
2543
2544
        $sessionCondition = api_get_session_condition($sessionId);
2545
        $table = Database::get_course_table(TABLE_LP_VIEW);
2546
        $sql = "SELECT * FROM $table
2547
                WHERE
2548
                    c_id = $courseId AND
2549
                    lp_id = $lpId AND
2550
                    user_id = $userId $sessionCondition ";
2551
        $res = Database::query($sql);
2552
        if (Database::num_rows($res) > 0) {
2553
            $row = Database:: fetch_array($res);
2554
            $progress = $row['progress'];
2555
        }
2556
2557
        return (int) $progress;
2558
    }
2559
2560
    /**
2561
     * Displays a progress bar
2562
     * completed so far.
2563
     *
2564
     * @param int    $percentage Progress value to display
2565
     * @param string $text_add   Text to display near the progress value
2566
     *
2567
     * @return string HTML string containing the progress bar
2568
     */
2569
    public static function get_progress_bar($percentage = -1, $text_add = '')
2570
    {
2571
        $text = $percentage.$text_add;
2572
        $output = '<div class="progress">
2573
            <div id="progress_bar_value" 
2574
                class="progress-bar progress-bar-success" role="progressbar" 
2575
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2576
            '.$text.'
2577
            </div>
2578
        </div>';
2579
2580
        return $output;
2581
    }
2582
2583
    /**
2584
     * @param string $mode can be '%' or 'abs'
2585
     *                     otherwise this value will be used $this->progress_bar_mode
2586
     *
2587
     * @return string
2588
     */
2589
    public function getProgressBar($mode = null)
2590
    {
2591
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2592
2593
        return self::get_progress_bar($percentage, $text_add);
2594
    }
2595
2596
    /**
2597
     * Gets the progress bar info to display inside the progress bar.
2598
     * Also used by scorm_api.php.
2599
     *
2600
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2601
     *                     we display a number of completed elements per total elements
2602
     * @param int    $add  Additional steps to fake as completed
2603
     *
2604
     * @return array Percentage or number and symbol (% or /xx)
2605
     */
2606
    public function get_progress_bar_text($mode = '', $add = 0)
2607
    {
2608
        if ($this->debug > 0) {
2609
            error_log('In learnpath::get_progress_bar_text()', 0);
2610
        }
2611
        if (empty($mode)) {
2612
            $mode = $this->progress_bar_mode;
2613
        }
2614
        $total_items = $this->getTotalItemsCountWithoutDirs();
2615
        if ($this->debug > 2) {
2616
            error_log('Total items available in this learnpath: '.$total_items, 0);
2617
        }
2618
        $completeItems = $this->get_complete_items_count();
2619
        if ($this->debug > 2) {
2620
            error_log('Items completed so far: '.$completeItems, 0);
2621
        }
2622
        if ($add != 0) {
2623
            $completeItems += $add;
2624
            if ($this->debug > 2) {
2625
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2626
            }
2627
        }
2628
        $text = '';
2629
        if ($completeItems > $total_items) {
2630
            $completeItems = $total_items;
2631
        }
2632
        $percentage = 0;
2633
        if ($mode == '%') {
2634
            if ($total_items > 0) {
2635
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2636
            } else {
2637
                $percentage = 0;
2638
            }
2639
            $percentage = number_format($percentage, 0);
2640
            $text = '%';
2641
        } elseif ($mode == 'abs') {
2642
            $percentage = $completeItems;
2643
            $text = '/'.$total_items;
2644
        }
2645
2646
        return [
2647
            $percentage,
2648
            $text,
2649
        ];
2650
    }
2651
2652
    /**
2653
     * Gets the progress bar mode.
2654
     *
2655
     * @return string The progress bar mode attribute
2656
     */
2657
    public function get_progress_bar_mode()
2658
    {
2659
        if ($this->debug > 0) {
2660
            error_log('In learnpath::get_progress_bar_mode()', 0);
2661
        }
2662
        if (!empty($this->progress_bar_mode)) {
2663
            return $this->progress_bar_mode;
2664
        } else {
2665
            return '%';
2666
        }
2667
    }
2668
2669
    /**
2670
     * Gets the learnpath theme (remote or local).
2671
     *
2672
     * @return string Learnpath theme
2673
     */
2674
    public function get_theme()
2675
    {
2676
        if ($this->debug > 0) {
2677
            error_log('In learnpath::get_theme()', 0);
2678
        }
2679
        if (!empty($this->theme)) {
2680
            return $this->theme;
2681
        } else {
2682
            return '';
2683
        }
2684
    }
2685
2686
    /**
2687
     * Gets the learnpath session id.
2688
     *
2689
     * @return int
2690
     */
2691
    public function get_lp_session_id()
2692
    {
2693
        if ($this->debug > 0) {
2694
            error_log('In learnpath::get_lp_session_id()', 0);
2695
        }
2696
        if (!empty($this->lp_session_id)) {
2697
            return (int) $this->lp_session_id;
2698
        } else {
2699
            return 0;
2700
        }
2701
    }
2702
2703
    /**
2704
     * Gets the learnpath image.
2705
     *
2706
     * @return string Web URL of the LP image
2707
     */
2708
    public function get_preview_image()
2709
    {
2710
        if ($this->debug > 0) {
2711
            error_log('In learnpath::get_preview_image()', 0);
2712
        }
2713
        if (!empty($this->preview_image)) {
2714
            return $this->preview_image;
2715
        } else {
2716
            return '';
2717
        }
2718
    }
2719
2720
    /**
2721
     * @param string $size
2722
     * @param string $path_type
2723
     *
2724
     * @return bool|string
2725
     */
2726
    public function get_preview_image_path($size = null, $path_type = 'web')
2727
    {
2728
        $preview_image = $this->get_preview_image();
2729
        if (isset($preview_image) && !empty($preview_image)) {
2730
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2731
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2732
2733
            if (isset($size)) {
2734
                $info = pathinfo($preview_image);
2735
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2736
2737
                if (file_exists($image_sys_path.$image_custom_size)) {
2738
                    if ($path_type == 'web') {
2739
                        return $image_path.$image_custom_size;
2740
                    } else {
2741
                        return $image_sys_path.$image_custom_size;
2742
                    }
2743
                }
2744
            } else {
2745
                if ($path_type == 'web') {
2746
                    return $image_path.$preview_image;
2747
                } else {
2748
                    return $image_sys_path.$preview_image;
2749
                }
2750
            }
2751
        }
2752
2753
        return false;
2754
    }
2755
2756
    /**
2757
     * Gets the learnpath author.
2758
     *
2759
     * @return string LP's author
2760
     */
2761
    public function get_author()
2762
    {
2763
        if ($this->debug > 0) {
2764
            error_log('In learnpath::get_author()', 0);
2765
        }
2766
        if (!empty($this->author)) {
2767
            return $this->author;
2768
        } else {
2769
            return '';
2770
        }
2771
    }
2772
2773
    /**
2774
     * Gets hide table of contents.
2775
     *
2776
     * @return int
2777
     */
2778
    public function getHideTableOfContents()
2779
    {
2780
        return (int) $this->hide_toc_frame;
2781
    }
2782
2783
    /**
2784
     * Generate a new prerequisites string for a given item. If this item was a sco and
2785
     * its prerequisites were strings (instead of IDs), then transform those strings into
2786
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2787
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2788
     * same rule as the scorm_export() method.
2789
     *
2790
     * @param int $item_id Item ID
2791
     *
2792
     * @return string Prerequisites string ready for the export as SCORM
2793
     */
2794
    public function get_scorm_prereq_string($item_id)
2795
    {
2796
        if ($this->debug > 0) {
2797
            error_log('In learnpath::get_scorm_prereq_string()');
2798
        }
2799
        if (!is_object($this->items[$item_id])) {
2800
            return false;
2801
        }
2802
        /** @var learnpathItem $oItem */
2803
        $oItem = $this->items[$item_id];
2804
        $prereq = $oItem->get_prereq_string();
2805
2806
        if (empty($prereq)) {
2807
            return '';
2808
        }
2809
        if (preg_match('/^\d+$/', $prereq) &&
2810
            isset($this->items[$prereq]) &&
2811
            is_object($this->items[$prereq])
2812
        ) {
2813
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2814
            // then simply return it (with the ITEM_ prefix).
2815
            //return 'ITEM_' . $prereq;
2816
            return $this->items[$prereq]->ref;
2817
        } else {
2818
            if (isset($this->refs_list[$prereq])) {
2819
                // It's a simple string item from which the ID can be found in the refs list,
2820
                // so we can transform it directly to an ID for export.
2821
                return $this->items[$this->refs_list[$prereq]]->ref;
2822
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2823
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2824
            } else {
2825
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2826
                // and replace them, one by one, by the internal IDs (chamilo db)
2827
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2828
                // by a space as well.
2829
                $find = [
2830
                    '&',
2831
                    '|',
2832
                    '~',
2833
                    '=',
2834
                    '<>',
2835
                    '{',
2836
                    '}',
2837
                    '*',
2838
                    '(',
2839
                    ')',
2840
                ];
2841
                $replace = [
2842
                    ' ',
2843
                    ' ',
2844
                    ' ',
2845
                    ' ',
2846
                    ' ',
2847
                    ' ',
2848
                    ' ',
2849
                    ' ',
2850
                    ' ',
2851
                    ' ',
2852
                ];
2853
                $prereq_mod = str_replace($find, $replace, $prereq);
2854
                $ids = explode(' ', $prereq_mod);
2855
                foreach ($ids as $id) {
2856
                    $id = trim($id);
2857
                    if (isset($this->refs_list[$id])) {
2858
                        $prereq = preg_replace(
2859
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2860
                            'ITEM_'.$this->refs_list[$id],
2861
                            $prereq
2862
                        );
2863
                    }
2864
                }
2865
2866
                return $prereq;
2867
            }
2868
        }
2869
    }
2870
2871
    /**
2872
     * Returns the XML DOM document's node.
2873
     *
2874
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2875
     * @param string   $id       The identifier to look for
2876
     *
2877
     * @return mixed The reference to the element found with that identifier. False if not found
2878
     */
2879
    public function get_scorm_xml_node(&$children, $id)
2880
    {
2881
        for ($i = 0; $i < $children->length; $i++) {
2882
            $item_temp = $children->item($i);
2883
            if ($item_temp->nodeName == 'item') {
2884
                if ($item_temp->getAttribute('identifier') == $id) {
2885
                    return $item_temp;
2886
                }
2887
            }
2888
            $subchildren = $item_temp->childNodes;
2889
            if ($subchildren && $subchildren->length > 0) {
2890
                $val = $this->get_scorm_xml_node($subchildren, $id);
2891
                if (is_object($val)) {
2892
                    return $val;
2893
                }
2894
            }
2895
        }
2896
2897
        return false;
2898
    }
2899
2900
    /**
2901
     * Gets the status list for all LP's items.
2902
     *
2903
     * @return array Array of [index] => [item ID => current status]
2904
     */
2905
    public function get_items_status_list()
2906
    {
2907
        if ($this->debug > 0) {
2908
            error_log('In learnpath::get_items_status_list()', 0);
2909
        }
2910
        $list = [];
2911
        foreach ($this->ordered_items as $item_id) {
2912
            $list[] = [
2913
                $item_id => $this->items[$item_id]->get_status(),
2914
            ];
2915
        }
2916
2917
        return $list;
2918
    }
2919
2920
    /**
2921
     * Return the number of interactions for the given learnpath Item View ID.
2922
     * This method can be used as static.
2923
     *
2924
     * @param int $lp_iv_id  Item View ID
2925
     * @param int $course_id course id
2926
     *
2927
     * @return int
2928
     */
2929
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2930
    {
2931
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2932
        $lp_iv_id = intval($lp_iv_id);
2933
        $course_id = intval($course_id);
2934
2935
        $sql = "SELECT count(*) FROM $table
2936
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2937
        $res = Database::query($sql);
2938
        $num = 0;
2939
        if (Database::num_rows($res)) {
2940
            $row = Database::fetch_array($res);
2941
            $num = $row[0];
2942
        }
2943
2944
        return $num;
2945
    }
2946
2947
    /**
2948
     * Return the interactions as an array for the given lp_iv_id.
2949
     * This method can be used as static.
2950
     *
2951
     * @param int $lp_iv_id Learnpath Item View ID
2952
     *
2953
     * @return array
2954
     *
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 $lp_iv_id  Item View ID
3006
     * @param int $course_id Course ID
3007
     *
3008
     * @return int Number of objectives
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
     * @todo    Translate labels
3037
     */
3038
    public static function get_iv_objectives_array($lpItemViewId = 0)
3039
    {
3040
        $course_id = api_get_course_int_id();
3041
        $lpItemViewId = (int) $lpItemViewId;
3042
3043
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3044
        $sql = "SELECT * FROM $table
3045
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3046
                ORDER BY order_id ASC";
3047
        $res = Database::query($sql);
3048
        $num = Database::num_rows($res);
3049
        $list = [];
3050
        if ($num > 0) {
3051
            $list[] = [
3052
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3053
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3054
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3055
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3056
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3057
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3058
            ];
3059
            while ($row = Database::fetch_array($res)) {
3060
                $list[] = [
3061
                    'order_id' => ($row['order_id'] + 1),
3062
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3063
                    'score_raw' => $row['score_raw'],
3064
                    'score_max' => $row['score_max'],
3065
                    'score_min' => $row['score_min'],
3066
                    'status' => $row['status'],
3067
                ];
3068
            }
3069
        }
3070
3071
        return $list;
3072
    }
3073
3074
    /**
3075
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3076
     * used by get_html_toc() to be ready to display.
3077
     *
3078
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3079
     */
3080
    public function get_toc()
3081
    {
3082
        if ($this->debug > 0) {
3083
            error_log('learnpath::get_toc()', 0);
3084
        }
3085
        $toc = [];
3086
        foreach ($this->ordered_items as $item_id) {
3087
            if ($this->debug > 2) {
3088
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3089
            }
3090
            // TODO: Change this link generation and use new function instead.
3091
            $toc[] = [
3092
                'id' => $item_id,
3093
                'title' => $this->items[$item_id]->get_title(),
3094
                'status' => $this->items[$item_id]->get_status(),
3095
                'level' => $this->items[$item_id]->get_level(),
3096
                'type' => $this->items[$item_id]->get_type(),
3097
                'description' => $this->items[$item_id]->get_description(),
3098
                'path' => $this->items[$item_id]->get_path(),
3099
                'parent' => $this->items[$item_id]->get_parent(),
3100
            ];
3101
        }
3102
        if ($this->debug > 2) {
3103
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3104
        }
3105
3106
        return $toc;
3107
    }
3108
3109
    /**
3110
     * Generate and return the table of contents for this learnpath. The JS
3111
     * table returned is used inside of scorm_api.php.
3112
     *
3113
     * @param string $varname
3114
     *
3115
     * @return string A JS array variable construction
3116
     */
3117
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3118
    {
3119
        if ($this->debug > 0) {
3120
            error_log('In learnpath::get_items_details_as_js()', 0);
3121
        }
3122
        $toc = $varname.' = new Array();';
3123
        foreach ($this->ordered_items as $item_id) {
3124
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3125
        }
3126
        if ($this->debug > 2) {
3127
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3128
        }
3129
3130
        return $toc;
3131
    }
3132
3133
    /**
3134
     * Gets the learning path type.
3135
     *
3136
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3137
     *
3138
     * @return mixed Type ID or name, depending on the parameter
3139
     */
3140
    public function get_type($get_name = false)
3141
    {
3142
        $res = false;
3143
        if ($this->debug > 0) {
3144
            error_log('In learnpath::get_type()', 0);
3145
        }
3146
        if (!empty($this->type) && (!$get_name)) {
3147
            $res = $this->type;
3148
        }
3149
        if ($this->debug > 2) {
3150
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3151
        }
3152
3153
        return $res;
3154
    }
3155
3156
    /**
3157
     * Gets the learning path type as static method.
3158
     *
3159
     * @param int $lp_id
3160
     *
3161
     * @return mixed Type ID or name, depending on the parameter
3162
     */
3163
    public static function get_type_static($lp_id = 0)
3164
    {
3165
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3166
        $lp_id = intval($lp_id);
3167
        $sql = "SELECT lp_type FROM $tbl_lp
3168
                WHERE iid = $lp_id";
3169
        $res = Database::query($sql);
3170
        if ($res === false) {
3171
            return null;
3172
        }
3173
        if (Database::num_rows($res) <= 0) {
3174
            return null;
3175
        }
3176
        $row = Database::fetch_array($res);
3177
3178
        return $row['lp_type'];
3179
    }
3180
3181
    /**
3182
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3183
     * This method can be used as abstract and is recursive.
3184
     *
3185
     * @param int $lp        Learnpath ID
3186
     * @param int $parent    Parent ID of the items to look for
3187
     * @param int $course_id
3188
     *
3189
     * @return array Ordered list of item IDs (empty array on error)
3190
     */
3191
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3192
    {
3193
        if (empty($course_id)) {
3194
            $course_id = api_get_course_int_id();
3195
        } else {
3196
            $course_id = (int) $course_id;
3197
        }
3198
        $list = [];
3199
3200
        if (empty($lp)) {
3201
            return $list;
3202
        }
3203
3204
        $lp = (int) $lp;
3205
        $parent = (int) $parent;
3206
3207
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3208
        $sql = "SELECT iid FROM $tbl_lp_item
3209
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3210
                ORDER BY display_order";
3211
3212
        $res = Database::query($sql);
3213
        while ($row = Database::fetch_array($res)) {
3214
            $sublist = self::get_flat_ordered_items_list(
3215
                $lp,
3216
                $row['iid'],
3217
                $course_id
3218
            );
3219
            $list[] = $row['iid'];
3220
            foreach ($sublist as $item) {
3221
                $list[] = $item;
3222
            }
3223
        }
3224
3225
        return $list;
3226
    }
3227
3228
    /**
3229
     * @return array
3230
     */
3231
    public static function getChapterTypes()
3232
    {
3233
        return [
3234
            'dir',
3235
        ];
3236
    }
3237
3238
    /**
3239
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3240
     *
3241
     * @param $tree
3242
     *
3243
     * @return array HTML TOC ready to display
3244
     */
3245
    public function getParentToc($tree)
3246
    {
3247
        if ($this->debug > 0) {
3248
            error_log('In learnpath::get_html_toc()', 0);
3249
        }
3250
        if (empty($tree)) {
3251
            $tree = $this->get_toc();
3252
        }
3253
        $dirTypes = self::getChapterTypes();
3254
        $myCurrentId = $this->get_current_item_id();
3255
        $listParent = [];
3256
        $listChildren = [];
3257
        $listNotParent = [];
3258
        $list = [];
3259
        foreach ($tree as $subtree) {
3260
            if (in_array($subtree['type'], $dirTypes)) {
3261
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3262
                $subtree['children'] = $listChildren;
3263
                if (!empty($subtree['children'])) {
3264
                    foreach ($subtree['children'] as $subItem) {
3265
                        if ($subItem['id'] == $this->current) {
3266
                            $subtree['parent_current'] = 'in';
3267
                            $subtree['current'] = 'on';
3268
                        }
3269
                    }
3270
                }
3271
                $listParent[] = $subtree;
3272
            }
3273
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3274
                $classStatus = [
3275
                    'not attempted' => 'scorm_not_attempted',
3276
                    'incomplete' => 'scorm_not_attempted',
3277
                    'failed' => 'scorm_failed',
3278
                    'completed' => 'scorm_completed',
3279
                    'passed' => 'scorm_completed',
3280
                    'succeeded' => 'scorm_completed',
3281
                    'browsed' => 'scorm_completed',
3282
                ];
3283
3284
                if (isset($classStatus[$subtree['status']])) {
3285
                    $cssStatus = $classStatus[$subtree['status']];
3286
                }
3287
3288
                $title = Security::remove_XSS($subtree['title']);
3289
                unset($subtree['title']);
3290
3291
                if (empty($title)) {
3292
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3293
                }
3294
                $classStyle = null;
3295
                if ($subtree['id'] == $this->current) {
3296
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3297
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3298
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3299
                }
3300
                $subtree['title'] = $title;
3301
                $subtree['class'] = $classStyle.' '.$cssStatus;
3302
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3303
                $subtree['current_id'] = $myCurrentId;
3304
                $listNotParent[] = $subtree;
3305
            }
3306
        }
3307
3308
        $list['are_parents'] = $listParent;
3309
        $list['not_parents'] = $listNotParent;
3310
3311
        return $list;
3312
    }
3313
3314
    /**
3315
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3316
     *
3317
     * @param array $tree
3318
     * @param int   $id
3319
     * @param bool  $parent
3320
     *
3321
     * @return array HTML TOC ready to display
3322
     */
3323
    public function getChildrenToc($tree, $id, $parent = true)
3324
    {
3325
        if ($this->debug > 0) {
3326
            error_log('In learnpath::get_html_toc()', 0);
3327
        }
3328
        if (empty($tree)) {
3329
            $tree = $this->get_toc();
3330
        }
3331
3332
        $dirTypes = self::getChapterTypes();
3333
        $mycurrentitemid = $this->get_current_item_id();
3334
        $list = [];
3335
        $classStatus = [
3336
            'not attempted' => 'scorm_not_attempted',
3337
            'incomplete' => 'scorm_not_attempted',
3338
            'failed' => 'scorm_failed',
3339
            'completed' => 'scorm_completed',
3340
            'passed' => 'scorm_completed',
3341
            'succeeded' => 'scorm_completed',
3342
            'browsed' => 'scorm_completed',
3343
        ];
3344
3345
        foreach ($tree as $subtree) {
3346
            $subtree['tree'] = null;
3347
3348
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3349
                if ($subtree['id'] == $this->current) {
3350
                    $subtree['current'] = 'active';
3351
                } else {
3352
                    $subtree['current'] = null;
3353
                }
3354
                if (isset($classStatus[$subtree['status']])) {
3355
                    $cssStatus = $classStatus[$subtree['status']];
3356
                }
3357
3358
                $title = Security::remove_XSS($subtree['title']);
3359
                unset($subtree['title']);
3360
                if (empty($title)) {
3361
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3362
                }
3363
3364
                $classStyle = null;
3365
                if ($subtree['id'] == $this->current) {
3366
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3367
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3368
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3369
                }
3370
3371
                if (in_array($subtree['type'], $dirTypes)) {
3372
                    $subtree['title'] = stripslashes($title);
3373
                } else {
3374
                    $subtree['title'] = $title;
3375
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3376
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3377
                    $subtree['current_id'] = $mycurrentitemid;
3378
                }
3379
                $list[] = $subtree;
3380
            }
3381
        }
3382
3383
        return $list;
3384
    }
3385
3386
    /**
3387
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3388
     *
3389
     * @param array $toc_list
3390
     *
3391
     * @return array HTML TOC ready to display
3392
     */
3393
    public function getListArrayToc($toc_list = [])
3394
    {
3395
        if ($this->debug > 0) {
3396
            error_log('In learnpath::get_html_toc()', 0);
3397
        }
3398
        if (empty($toc_list)) {
3399
            $toc_list = $this->get_toc();
3400
        }
3401
        // Temporary variables.
3402
        $mycurrentitemid = $this->get_current_item_id();
3403
        $list = [];
3404
        $arrayList = [];
3405
        $classStatus = [
3406
            'not attempted' => 'scorm_not_attempted',
3407
            'incomplete' => 'scorm_not_attempted',
3408
            'failed' => 'scorm_failed',
3409
            'completed' => 'scorm_completed',
3410
            'passed' => 'scorm_completed',
3411
            'succeeded' => 'scorm_completed',
3412
            'browsed' => 'scorm_completed',
3413
        ];
3414
3415
        foreach ($toc_list as $item) {
3416
            $list['id'] = $item['id'];
3417
            $list['status'] = $item['status'];
3418
            $cssStatus = null;
3419
3420
            if (isset($classStatus[$item['status']])) {
3421
                $cssStatus = $classStatus[$item['status']];
3422
            }
3423
3424
            $classStyle = ' ';
3425
            $dirTypes = self::getChapterTypes();
3426
3427
            if (in_array($item['type'], $dirTypes)) {
3428
                $classStyle = 'scorm_item_section ';
3429
            }
3430
            if ($item['id'] == $this->current) {
3431
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3432
            } elseif (!in_array($item['type'], $dirTypes)) {
3433
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3434
            }
3435
            $title = $item['title'];
3436
            if (empty($title)) {
3437
                $title = self::rl_get_resource_name(
3438
                    api_get_course_id(),
3439
                    $this->get_id(),
3440
                    $item['id']
3441
                );
3442
            }
3443
            $title = Security::remove_XSS($item['title']);
3444
3445
            if (empty($item['description'])) {
3446
                $list['description'] = $title;
3447
            } else {
3448
                $list['description'] = $item['description'];
3449
            }
3450
3451
            $list['class'] = $classStyle.' '.$cssStatus;
3452
            $list['level'] = $item['level'];
3453
            $list['type'] = $item['type'];
3454
3455
            if (in_array($item['type'], $dirTypes)) {
3456
                $list['css_level'] = 'level_'.$item['level'];
3457
            } else {
3458
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.learnpath::format_scorm_type_item($item['type']);
3459
            }
3460
3461
            if (in_array($item['type'], $dirTypes)) {
3462
                $list['title'] = stripslashes($title);
3463
            } else {
3464
                $list['title'] = stripslashes($title);
3465
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3466
                $list['current_id'] = $mycurrentitemid;
3467
            }
3468
            $arrayList[] = $list;
3469
        }
3470
3471
        return $arrayList;
3472
    }
3473
3474
    /**
3475
     * Returns an HTML-formatted string ready to display with teacher buttons
3476
     * in LP view menu.
3477
     *
3478
     * @return string HTML TOC ready to display
3479
     */
3480
    public function get_teacher_toc_buttons()
3481
    {
3482
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3483
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3484
        $html = '';
3485
        if ($isAllow && $hideIcons == false) {
3486
            if ($this->get_lp_session_id() == api_get_session_id()) {
3487
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3488
                $html .= '<div class="btn-group">';
3489
                $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'>".
3490
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3491
                $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'>".
3492
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3493
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3494
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3495
                $html .= '</div>';
3496
                $html .= '</div>';
3497
            }
3498
        }
3499
3500
        return $html;
3501
    }
3502
3503
    /**
3504
     * Gets the learnpath maker name - generally the editor's name.
3505
     *
3506
     * @return string Learnpath maker name
3507
     */
3508
    public function get_maker()
3509
    {
3510
        if ($this->debug > 0) {
3511
            error_log('In learnpath::get_maker()', 0);
3512
        }
3513
        if (!empty($this->maker)) {
3514
            return $this->maker;
3515
        } else {
3516
            return '';
3517
        }
3518
    }
3519
3520
    /**
3521
     * Gets the learnpath name/title.
3522
     *
3523
     * @return string Learnpath name/title
3524
     */
3525
    public function get_name()
3526
    {
3527
        if ($this->debug > 0) {
3528
            error_log('In learnpath::get_name()', 0);
3529
        }
3530
        if (!empty($this->name)) {
3531
            return $this->name;
3532
        } else {
3533
            return 'N/A';
3534
        }
3535
    }
3536
3537
    /**
3538
     * Gets a link to the resource from the present location, depending on item ID.
3539
     *
3540
     * @param string $type         Type of link expected
3541
     * @param int    $item_id      Learnpath item ID
3542
     * @param bool   $provided_toc
3543
     *
3544
     * @return string $provided_toc Link to the lp_item resource
3545
     */
3546
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3547
    {
3548
        $course_id = $this->get_course_int_id();
3549
        if ($this->debug > 0) {
3550
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3551
        }
3552
        if (empty($item_id)) {
3553
            if ($this->debug > 2) {
3554
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3555
                error_log('using current: '.$this->get_current_item_id(), 0);
3556
            }
3557
            $item_id = $this->get_current_item_id();
3558
        }
3559
3560
        if (empty($item_id)) {
3561
            if ($this->debug > 2) {
3562
                error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3563
            }
3564
            //still empty, this means there was no item_id given and we are not in an object context or
3565
            //the object property is empty, return empty link
3566
            $this->first();
3567
3568
            return '';
3569
        }
3570
3571
        $file = '';
3572
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3573
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3574
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3575
        $item_id = intval($item_id);
3576
3577
        $sql = "SELECT
3578
                    l.lp_type as ltype,
3579
                    l.path as lpath,
3580
                    li.item_type as litype,
3581
                    li.path as lipath,
3582
                    li.parameters as liparams
3583
        		FROM $lp_table l
3584
                INNER JOIN $lp_item_table li
3585
                ON (li.lp_id = l.iid)
3586
        		WHERE 
3587
        		    li.iid = $item_id 
3588
        		";
3589
        if ($this->debug > 2) {
3590
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3591
        }
3592
        $res = Database::query($sql);
3593
        if (Database::num_rows($res) > 0) {
3594
            $row = Database::fetch_array($res);
3595
            $lp_type = $row['ltype'];
3596
            $lp_path = $row['lpath'];
3597
            $lp_item_type = $row['litype'];
3598
            $lp_item_path = $row['lipath'];
3599
            $lp_item_params = $row['liparams'];
3600
3601
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3602
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3603
            }
3604
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3605
            if ($type == 'http') {
3606
                //web path
3607
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3608
            } else {
3609
                $course_path = $sys_course_path; //system path
3610
            }
3611
3612
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3613
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3614
            if (in_array(
3615
                $lp_item_type,
3616
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3617
            )
3618
            ) {
3619
                $lp_type = 1;
3620
            }
3621
3622
            if ($this->debug > 2) {
3623
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3624
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3625
            }
3626
3627
            // Now go through the specific cases to get the end of the path
3628
            // @todo Use constants instead of int values.
3629
3630
            switch ($lp_type) {
3631
                case 1:
3632
                    $file = self::rl_get_resource_link_for_learnpath(
3633
                        $course_id,
3634
                        $this->get_id(),
3635
                        $item_id,
3636
                        $this->get_view_id()
3637
                    );
3638
3639
                    if ($this->debug > 0) {
3640
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3641
                    }
3642
3643
                    switch ($lp_item_type) {
3644
                        case 'dir':
3645
                            $file = 'lp_content.php?type=dir';
3646
                            break;
3647
                        case 'link':
3648
                            if (Link::is_youtube_link($file)) {
3649
                                $src = Link::get_youtube_video_id($file);
3650
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3651
                            } elseif (Link::isVimeoLink($file)) {
3652
                                $src = Link::getVimeoLinkId($file);
3653
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3654
                            } else {
3655
                                // If the current site is HTTPS and the link is
3656
                                // HTTP, browsers will refuse opening the link
3657
                                $urlId = api_get_current_access_url_id();
3658
                                $url = api_get_access_url($urlId, false);
3659
                                $protocol = substr($url['url'], 0, 5);
3660
                                if ($protocol === 'https') {
3661
                                    $linkProtocol = substr($file, 0, 5);
3662
                                    if ($linkProtocol === 'http:') {
3663
                                        //this is the special intervention case
3664
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3665
                                    }
3666
                                }
3667
                            }
3668
                            break;
3669
                        case 'quiz':
3670
                            // Check how much attempts of a exercise exits in lp
3671
                            $lp_item_id = $this->get_current_item_id();
3672
                            $lp_view_id = $this->get_view_id();
3673
3674
                            $prevent_reinit = null;
3675
                            if (isset($this->items[$this->current])) {
3676
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3677
                            }
3678
3679
                            if (empty($provided_toc)) {
3680
                                if ($this->debug > 0) {
3681
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3682
                                }
3683
                                $list = $this->get_toc();
3684
                            } else {
3685
                                if ($this->debug > 0) {
3686
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3687
                                }
3688
                                $list = $provided_toc;
3689
                            }
3690
3691
                            $type_quiz = false;
3692
3693
                            foreach ($list as $toc) {
3694
                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
3695
                                    $type_quiz = true;
3696
                                }
3697
                            }
3698
3699
                            if ($type_quiz) {
3700
                                $lp_item_id = intval($lp_item_id);
3701
                                $lp_view_id = intval($lp_view_id);
3702
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3703
                                        WHERE
3704
                                            c_id = $course_id AND
3705
                                            lp_item_id='".$lp_item_id."' AND
3706
                                            lp_view_id ='".$lp_view_id."' AND
3707
                                            status='completed'";
3708
                                $result = Database::query($sql);
3709
                                $row_count = Database:: fetch_row($result);
3710
                                $count_item_view = (int) $row_count[0];
3711
                                $not_multiple_attempt = 0;
3712
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3713
                                    $not_multiple_attempt = 1;
3714
                                }
3715
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3716
                            }
3717
                            break;
3718
                    }
3719
3720
                    $tmp_array = explode('/', $file);
3721
                    $document_name = $tmp_array[count($tmp_array) - 1];
3722
                    if (strpos($document_name, '_DELETED_')) {
3723
                        $file = 'blank.php?error=document_deleted';
3724
                    }
3725
3726
                    break;
3727
                case 2:
3728
                    if ($this->debug > 2) {
3729
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3730
                    }
3731
3732
                    if ($lp_item_type != 'dir') {
3733
                        // Quite complex here:
3734
                        // We want to make sure 'http://' (and similar) links can
3735
                        // be loaded as is (withouth the Chamilo path in front) but
3736
                        // some contents use this form: resource.htm?resource=http://blablabla
3737
                        // which means we have to find a protocol at the path's start, otherwise
3738
                        // it should not be considered as an external URL.
3739
                        // if ($this->prerequisites_match($item_id)) {
3740
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3741
                            if ($this->debug > 2) {
3742
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3743
                            }
3744
                            // Distant url, return as is.
3745
                            $file = $lp_item_path;
3746
                        } else {
3747
                            if ($this->debug > 2) {
3748
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3749
                            }
3750
                            // Prevent getting untranslatable urls.
3751
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3752
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3753
                            // Prepare the path.
3754
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3755
                            // TODO: Fix this for urls with protocol header.
3756
                            $file = str_replace('//', '/', $file);
3757
                            $file = str_replace(':/', '://', $file);
3758
                            if (substr($lp_path, -1) == '/') {
3759
                                $lp_path = substr($lp_path, 0, -1);
3760
                            }
3761
3762
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3763
                                // if file not found.
3764
                                $decoded = html_entity_decode($lp_item_path);
3765
                                list($decoded) = explode('?', $decoded);
3766
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3767
                                    $file = self::rl_get_resource_link_for_learnpath(
3768
                                        $course_id,
3769
                                        $this->get_id(),
3770
                                        $item_id,
3771
                                        $this->get_view_id()
3772
                                    );
3773
                                    if (empty($file)) {
3774
                                        $file = 'blank.php?error=document_not_found';
3775
                                    } else {
3776
                                        $tmp_array = explode('/', $file);
3777
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3778
                                        if (strpos($document_name, '_DELETED_')) {
3779
                                            $file = 'blank.php?error=document_deleted';
3780
                                        } else {
3781
                                            $file = 'blank.php?error=document_not_found';
3782
                                        }
3783
                                    }
3784
                                } else {
3785
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3786
                                }
3787
                            }
3788
                        }
3789
3790
                        // We want to use parameters if they were defined in the imsmanifest
3791
                        if (strpos($file, 'blank.php') === false) {
3792
                            $lp_item_params = ltrim($lp_item_params, '?');
3793
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3794
                        }
3795
                    } else {
3796
                        $file = 'lp_content.php?type=dir';
3797
                    }
3798
                    break;
3799
                case 3:
3800
                    if ($this->debug > 2) {
3801
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3802
                    }
3803
                    // Formatting AICC HACP append URL.
3804
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3805
                    if (!empty($lp_item_params)) {
3806
                        $aicc_append .= $lp_item_params.'&';
3807
                    }
3808
                    if ($lp_item_type != 'dir') {
3809
                        // Quite complex here:
3810
                        // We want to make sure 'http://' (and similar) links can
3811
                        // be loaded as is (withouth the Chamilo path in front) but
3812
                        // some contents use this form: resource.htm?resource=http://blablabla
3813
                        // which means we have to find a protocol at the path's start, otherwise
3814
                        // it should not be considered as an external URL.
3815
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3816
                            if ($this->debug > 2) {
3817
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3818
                            }
3819
                            // Distant url, return as is.
3820
                            $file = $lp_item_path;
3821
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3822
                            /*
3823
                            if (stristr($file,'<servername>') !== false) {
3824
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3825
                            }
3826
                            */
3827
                            if (stripos($file, '<servername>') !== false) {
3828
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3829
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3830
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3831
                            }
3832
3833
                            $file .= $aicc_append;
3834
                        } else {
3835
                            if ($this->debug > 2) {
3836
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3837
                            }
3838
                            // Prevent getting untranslatable urls.
3839
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3840
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3841
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3842
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3843
                            // TODO: Fix this for urls with protocol header.
3844
                            $file = str_replace('//', '/', $file);
3845
                            $file = str_replace(':/', '://', $file);
3846
                            $file .= $aicc_append;
3847
                        }
3848
                    } else {
3849
                        $file = 'lp_content.php?type=dir';
3850
                    }
3851
                    break;
3852
                case 4:
3853
                    break;
3854
                default:
3855
                    break;
3856
            }
3857
            // Replace &amp; by & because &amp; will break URL with params
3858
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3859
        }
3860
        if ($this->debug > 2) {
3861
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3862
        }
3863
3864
        return $file;
3865
    }
3866
3867
    /**
3868
     * Gets the latest usable view or generate a new one.
3869
     *
3870
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3871
     *
3872
     * @return int DB lp_view id
3873
     */
3874
    public function get_view($attempt_num = 0)
3875
    {
3876
        if ($this->debug > 0) {
3877
            error_log('In learnpath::get_view()', 0);
3878
        }
3879
        $search = '';
3880
        // Use $attempt_num to enable multi-views management (disabled so far).
3881
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3882
            $search = 'AND view_count = '.$attempt_num;
3883
        }
3884
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3885
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3886
3887
        $course_id = api_get_course_int_id();
3888
        $sessionId = api_get_session_id();
3889
3890
        $sql = "SELECT iid, view_count FROM $lp_view_table
3891
        		WHERE
3892
        		    c_id = $course_id AND
3893
        		    lp_id = ".$this->get_id()." AND
3894
        		    user_id = ".$this->get_user_id()." AND
3895
        		    session_id = $sessionId
3896
        		    $search
3897
                ORDER BY view_count DESC";
3898
        $res = Database::query($sql);
3899
        if (Database::num_rows($res) > 0) {
3900
            $row = Database::fetch_array($res);
3901
            $this->lp_view_id = $row['iid'];
3902
        } elseif (!api_is_invitee()) {
3903
            // There is no database record, create one.
3904
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3905
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3906
            Database::query($sql);
3907
            $id = Database::insert_id();
3908
            $this->lp_view_id = $id;
3909
3910
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3911
            Database::query($sql);
3912
        }
3913
3914
        return $this->lp_view_id;
3915
    }
3916
3917
    /**
3918
     * Gets the current view id.
3919
     *
3920
     * @return int View ID (from lp_view)
3921
     */
3922
    public function get_view_id()
3923
    {
3924
        if ($this->debug > 0) {
3925
            error_log('In learnpath::get_view_id()', 0);
3926
        }
3927
        if (!empty($this->lp_view_id)) {
3928
            return $this->lp_view_id;
3929
        } else {
3930
            return 0;
3931
        }
3932
    }
3933
3934
    /**
3935
     * Gets the update queue.
3936
     *
3937
     * @return array Array containing IDs of items to be updated by JavaScript
3938
     */
3939
    public function get_update_queue()
3940
    {
3941
        if ($this->debug > 0) {
3942
            error_log('In learnpath::get_update_queue()', 0);
3943
        }
3944
3945
        return $this->update_queue;
3946
    }
3947
3948
    /**
3949
     * Gets the user ID.
3950
     *
3951
     * @return int User ID
3952
     */
3953
    public function get_user_id()
3954
    {
3955
        if ($this->debug > 0) {
3956
            error_log('In learnpath::get_user_id()', 0);
3957
        }
3958
        if (!empty($this->user_id)) {
3959
            return $this->user_id;
3960
        } else {
3961
            return false;
3962
        }
3963
    }
3964
3965
    /**
3966
     * Checks if any of the items has an audio element attached.
3967
     *
3968
     * @return bool True or false
3969
     */
3970
    public function has_audio()
3971
    {
3972
        if ($this->debug > 1) {
3973
            error_log('In learnpath::has_audio()', 0);
3974
        }
3975
        $has = false;
3976
        foreach ($this->items as $i => $item) {
3977
            if (!empty($this->items[$i]->audio)) {
3978
                $has = true;
3979
                break;
3980
            }
3981
        }
3982
3983
        return $has;
3984
    }
3985
3986
    /**
3987
     * Moves an item up and down at its level.
3988
     *
3989
     * @param int    $id        Item to move up and down
3990
     * @param string $direction Direction 'up' or 'down'
3991
     *
3992
     * @return bool|int
3993
     */
3994
    public function move_item($id, $direction)
3995
    {
3996
        $course_id = api_get_course_int_id();
3997
        if ($this->debug > 0) {
3998
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
3999
        }
4000
        if (empty($id) || empty($direction)) {
4001
            return false;
4002
        }
4003
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4004
        $sql_sel = "SELECT *
4005
                    FROM $tbl_lp_item
4006
                    WHERE  
4007
                        iid = $id
4008
                    ";
4009
        $res_sel = Database::query($sql_sel);
4010
        // Check if elem exists.
4011
        if (Database::num_rows($res_sel) < 1) {
4012
            return false;
4013
        }
4014
        // Gather data.
4015
        $row = Database::fetch_array($res_sel);
4016
        $previous = $row['previous_item_id'];
4017
        $next = $row['next_item_id'];
4018
        $display = $row['display_order'];
4019
        $parent = $row['parent_item_id'];
4020
        $lp = $row['lp_id'];
4021
        // Update the item (switch with previous/next one).
4022
        switch ($direction) {
4023
            case 'up':
4024
                if ($this->debug > 2) {
4025
                    error_log('Movement up detected', 0);
4026
                }
4027
                if ($display > 1) {
4028
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4029
                                 WHERE iid = $previous";
4030
4031
                    if ($this->debug > 2) {
4032
                        error_log('Selecting previous: '.$sql_sel2, 0);
4033
                    }
4034
                    $res_sel2 = Database::query($sql_sel2);
4035
                    if (Database::num_rows($res_sel2) < 1) {
4036
                        $previous_previous = 0;
4037
                    }
4038
                    // Gather data.
4039
                    $row2 = Database::fetch_array($res_sel2);
4040
                    $previous_previous = $row2['previous_item_id'];
4041
                    // Update previous_previous item (switch "next" with current).
4042
                    if ($previous_previous != 0) {
4043
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4044
                                        next_item_id = $id
4045
                                    WHERE iid = $previous_previous";
4046
                        if ($this->debug > 2) {
4047
                            error_log($sql_upd2, 0);
4048
                        }
4049
                        Database::query($sql_upd2);
4050
                    }
4051
                    // Update previous item (switch with current).
4052
                    if ($previous != 0) {
4053
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4054
                                    next_item_id = $next,
4055
                                    previous_item_id = $id,
4056
                                    display_order = display_order +1
4057
                                    WHERE iid = $previous";
4058
                        if ($this->debug > 2) {
4059
                            error_log($sql_upd2, 0);
4060
                        }
4061
                        Database::query($sql_upd2);
4062
                    }
4063
4064
                    // Update current item (switch with previous).
4065
                    if ($id != 0) {
4066
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4067
                                        next_item_id = $previous,
4068
                                        previous_item_id = $previous_previous,
4069
                                        display_order = display_order-1
4070
                                    WHERE c_id = ".$course_id." AND id = $id";
4071
                        if ($this->debug > 2) {
4072
                            error_log($sql_upd2, 0);
4073
                        }
4074
                        Database::query($sql_upd2);
4075
                    }
4076
                    // Update next item (new previous item).
4077
                    if ($next != 0) {
4078
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4079
                                     WHERE iid = $next";
4080
                        if ($this->debug > 2) {
4081
                            error_log($sql_upd2, 0);
4082
                        }
4083
                        Database::query($sql_upd2);
4084
                    }
4085
                    $display = $display - 1;
4086
                }
4087
                break;
4088
            case 'down':
4089
                if ($this->debug > 2) {
4090
                    error_log('Movement down detected', 0);
4091
                }
4092
                if ($next != 0) {
4093
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4094
                                 WHERE iid = $next";
4095
                    if ($this->debug > 2) {
4096
                        error_log('Selecting next: '.$sql_sel2, 0);
4097
                    }
4098
                    $res_sel2 = Database::query($sql_sel2);
4099
                    if (Database::num_rows($res_sel2) < 1) {
4100
                        $next_next = 0;
4101
                    }
4102
                    // Gather data.
4103
                    $row2 = Database::fetch_array($res_sel2);
4104
                    $next_next = $row2['next_item_id'];
4105
                    // Update previous item (switch with current).
4106
                    if ($previous != 0) {
4107
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4108
                                     SET next_item_id = $next
4109
                                     WHERE iid = $previous";
4110
                        Database::query($sql_upd2);
4111
                    }
4112
                    // Update current item (switch with previous).
4113
                    if ($id != 0) {
4114
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4115
                                     previous_item_id = $next, 
4116
                                     next_item_id = $next_next, 
4117
                                     display_order = display_order + 1
4118
                                     WHERE iid = $id";
4119
                        Database::query($sql_upd2);
4120
                    }
4121
4122
                    // Update next item (new previous item).
4123
                    if ($next != 0) {
4124
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4125
                                     previous_item_id = $previous, 
4126
                                     next_item_id = $id, 
4127
                                     display_order = display_order-1
4128
                                     WHERE iid = $next";
4129
                        Database::query($sql_upd2);
4130
                    }
4131
4132
                    // Update next_next item (switch "previous" with current).
4133
                    if ($next_next != 0) {
4134
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4135
                                     previous_item_id = $id
4136
                                     WHERE iid = $next_next";
4137
                        Database::query($sql_upd2);
4138
                    }
4139
                    $display = $display + 1;
4140
                }
4141
                break;
4142
            default:
4143
                return false;
4144
        }
4145
4146
        return $display;
4147
    }
4148
4149
    /**
4150
     * Move a LP up (display_order).
4151
     *
4152
     * @param int $lp_id      Learnpath ID
4153
     * @param int $categoryId Category ID
4154
     *
4155
     * @return bool
4156
     */
4157
    public static function move_up($lp_id, $categoryId = 0)
4158
    {
4159
        $courseId = api_get_course_int_id();
4160
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4161
4162
        $categoryCondition = '';
4163
        if (!empty($categoryId)) {
4164
            $categoryId = (int) $categoryId;
4165
            $categoryCondition = " AND category_id = $categoryId";
4166
        }
4167
        $sql = "SELECT * FROM $lp_table
4168
                WHERE c_id = $courseId
4169
                $categoryCondition
4170
                ORDER BY display_order";
4171
        $res = Database::query($sql);
4172
        if ($res === false) {
4173
            return false;
4174
        }
4175
4176
        $lps = [];
4177
        $lp_order = [];
4178
        $num = Database::num_rows($res);
4179
        // First check the order is correct, globally (might be wrong because
4180
        // of versions < 1.8.4)
4181
        if ($num > 0) {
4182
            $i = 1;
4183
            while ($row = Database::fetch_array($res)) {
4184
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4185
                    $sql = "UPDATE $lp_table SET display_order = $i
4186
                            WHERE iid = ".$row['iid'];
4187
                    Database::query($sql);
4188
                }
4189
                $row['display_order'] = $i;
4190
                $lps[$row['iid']] = $row;
4191
                $lp_order[$i] = $row['iid'];
4192
                $i++;
4193
            }
4194
        }
4195
        if ($num > 1) { // If there's only one element, no need to sort.
4196
            $order = $lps[$lp_id]['display_order'];
4197
            if ($order > 1) { // If it's the first element, no need to move up.
4198
                $sql = "UPDATE $lp_table SET display_order = $order
4199
                        WHERE iid = ".$lp_order[$order - 1];
4200
                Database::query($sql);
4201
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4202
                        WHERE iid = $lp_id";
4203
                Database::query($sql);
4204
            }
4205
        }
4206
4207
        return true;
4208
    }
4209
4210
    /**
4211
     * Move a learnpath down (display_order).
4212
     *
4213
     * @param int $lp_id      Learnpath ID
4214
     * @param int $categoryId Category ID
4215
     *
4216
     * @return bool
4217
     */
4218
    public static function move_down($lp_id, $categoryId = 0)
4219
    {
4220
        $courseId = api_get_course_int_id();
4221
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4222
4223
        $categoryCondition = '';
4224
        if (!empty($categoryId)) {
4225
            $categoryId = (int) $categoryId;
4226
            $categoryCondition = " AND category_id = $categoryId";
4227
        }
4228
4229
        $sql = "SELECT * FROM $lp_table
4230
                WHERE c_id = $courseId
4231
                $categoryCondition
4232
                ORDER BY display_order";
4233
        $res = Database::query($sql);
4234
        if ($res === false) {
4235
            return false;
4236
        }
4237
        $lps = [];
4238
        $lp_order = [];
4239
        $num = Database::num_rows($res);
4240
        $max = 0;
4241
        // First check the order is correct, globally (might be wrong because
4242
        // of versions < 1.8.4).
4243
        if ($num > 0) {
4244
            $i = 1;
4245
            while ($row = Database::fetch_array($res)) {
4246
                $max = $i;
4247
                if ($row['display_order'] != $i) {
4248
                    // If we find a gap in the order, we need to fix it.
4249
                    $sql = "UPDATE $lp_table SET display_order = $i
4250
                              WHERE iid = ".$row['iid'];
4251
                    Database::query($sql);
4252
                }
4253
                $row['display_order'] = $i;
4254
                $lps[$row['iid']] = $row;
4255
                $lp_order[$i] = $row['iid'];
4256
                $i++;
4257
            }
4258
        }
4259
        if ($num > 1) { // If there's only one element, no need to sort.
4260
            $order = $lps[$lp_id]['display_order'];
4261
            if ($order < $max) { // If it's the first element, no need to move up.
4262
                $sql = "UPDATE $lp_table SET display_order = $order
4263
                        WHERE iid = ".$lp_order[$order + 1];
4264
                Database::query($sql);
4265
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4266
                        WHERE iid = $lp_id";
4267
                Database::query($sql);
4268
            }
4269
        }
4270
4271
        return true;
4272
    }
4273
4274
    /**
4275
     * Updates learnpath attributes to point to the next element
4276
     * The last part is similar to set_current_item but processing the other way around.
4277
     */
4278
    public function next()
4279
    {
4280
        if ($this->debug > 0) {
4281
            error_log('In learnpath::next()', 0);
4282
        }
4283
        $this->last = $this->get_current_item_id();
4284
        $this->items[$this->last]->save(
4285
            false,
4286
            $this->prerequisites_match($this->last)
4287
        );
4288
        $this->autocomplete_parents($this->last);
4289
        $new_index = $this->get_next_index();
4290
        if ($this->debug > 2) {
4291
            error_log('New index: '.$new_index, 0);
4292
        }
4293
        $this->index = $new_index;
4294
        if ($this->debug > 2) {
4295
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4296
        }
4297
        $this->current = $this->ordered_items[$new_index];
4298
        if ($this->debug > 2) {
4299
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4300
        }
4301
    }
4302
4303
    /**
4304
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4305
     * class, this might be redefined to allow several behaviours depending on the document type.
4306
     *
4307
     * @param int $id Resource ID
4308
     */
4309
    public function open($id)
4310
    {
4311
        if ($this->debug > 0) {
4312
            error_log('In learnpath::open()', 0);
4313
        }
4314
        // TODO:
4315
        // set the current resource attribute to this resource
4316
        // switch on element type (redefine in child class?)
4317
        // set status for this item to "opened"
4318
        // start timer
4319
        // initialise score
4320
        $this->index = 0; //or = the last item seen (see $this->last)
4321
    }
4322
4323
    /**
4324
     * Check that all prerequisites are fulfilled. Returns true and an
4325
     * empty string on success, returns false
4326
     * and the prerequisite string on error.
4327
     * This function is based on the rules for aicc_script language as
4328
     * described in the SCORM 1.2 CAM documentation page 108.
4329
     *
4330
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4331
     *
4332
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4333
     *              string otherwise
4334
     */
4335
    public function prerequisites_match($itemId = null)
4336
    {
4337
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4338
        if ($allow) {
4339
            if (api_is_allowed_to_edit() ||
4340
                api_is_platform_admin() ||
4341
                api_is_drh() ||
4342
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4343
            ) {
4344
                return true;
4345
            }
4346
        }
4347
4348
        $debug = $this->debug;
4349
        if ($debug > 0) {
4350
            error_log('In learnpath::prerequisites_match()', 0);
4351
        }
4352
4353
        if (empty($itemId)) {
4354
            $itemId = $this->current;
4355
        }
4356
4357
        $currentItem = $this->getItem($itemId);
4358
4359
        if ($currentItem) {
4360
            if ($this->type == 2) {
4361
                // Getting prereq from scorm
4362
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4363
            } else {
4364
                $prereq_string = $currentItem->get_prereq_string();
4365
            }
4366
4367
            if (empty($prereq_string)) {
4368
                if ($debug > 0) {
4369
                    error_log('Found prereq_string is empty return true');
4370
                }
4371
4372
                return true;
4373
            }
4374
            // Clean spaces.
4375
            $prereq_string = str_replace(' ', '', $prereq_string);
4376
            if ($debug > 0) {
4377
                error_log('Found prereq_string: '.$prereq_string, 0);
4378
            }
4379
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4380
            $result = $currentItem->parse_prereq(
4381
                $prereq_string,
4382
                $this->items,
4383
                $this->refs_list,
4384
                $this->get_user_id()
4385
            );
4386
4387
            if ($result === false) {
4388
                $this->set_error_msg($currentItem->prereq_alert);
4389
            }
4390
        } else {
4391
            $result = true;
4392
            if ($debug > 1) {
4393
                error_log('$this->items['.$itemId.'] was not an object', 0);
4394
            }
4395
        }
4396
4397
        if ($debug > 1) {
4398
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4399
        }
4400
4401
        return $result;
4402
    }
4403
4404
    /**
4405
     * Updates learnpath attributes to point to the previous element
4406
     * The last part is similar to set_current_item but processing the other way around.
4407
     */
4408
    public function previous()
4409
    {
4410
        if ($this->debug > 0) {
4411
            error_log('In learnpath::previous()', 0);
4412
        }
4413
        $this->last = $this->get_current_item_id();
4414
        $this->items[$this->last]->save(
4415
            false,
4416
            $this->prerequisites_match($this->last)
4417
        );
4418
        $this->autocomplete_parents($this->last);
4419
        $new_index = $this->get_previous_index();
4420
        $this->index = $new_index;
4421
        $this->current = $this->ordered_items[$new_index];
4422
    }
4423
4424
    /**
4425
     * Publishes a learnpath. This basically means show or hide the learnpath
4426
     * to normal users.
4427
     * Can be used as abstract.
4428
     *
4429
     * @param int $lp_id          Learnpath ID
4430
     * @param int $set_visibility New visibility
4431
     *
4432
     * @return bool
4433
     */
4434
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4435
    {
4436
        $action = 'visible';
4437
        if ($set_visibility != 1) {
4438
            $action = 'invisible';
4439
            self::toggle_publish($lp_id, 'i');
4440
        }
4441
4442
        return api_item_property_update(
4443
            api_get_course_info(),
4444
            TOOL_LEARNPATH,
4445
            $lp_id,
4446
            $action,
4447
            api_get_user_id()
4448
        );
4449
    }
4450
4451
    /**
4452
     * Publishes a learnpath category.
4453
     * This basically means show or hide the learnpath category to normal users.
4454
     *
4455
     * @param int $id
4456
     * @param int $visibility
4457
     *
4458
     * @throws \Doctrine\ORM\NonUniqueResultException
4459
     * @throws \Doctrine\ORM\ORMException
4460
     * @throws \Doctrine\ORM\OptimisticLockException
4461
     * @throws \Doctrine\ORM\TransactionRequiredException
4462
     *
4463
     * @return bool
4464
     */
4465
    public static function toggleCategoryVisibility($id, $visibility = 1)
4466
    {
4467
        $action = 'visible';
4468
4469
        if ($visibility != 1) {
4470
            $action = 'invisible';
4471
            $list = new LearnpathList(
4472
                api_get_user_id(),
4473
                null,
4474
                null,
4475
                null,
4476
                false,
4477
                $id
4478
            );
4479
4480
            $lpList = $list->get_flat_list();
4481
            foreach ($lpList as $lp) {
4482
                learnpath::toggle_visibility($lp['iid'], 0);
4483
            }
4484
4485
            learnpath::toggleCategoryPublish($id, 0);
4486
        }
4487
4488
        return api_item_property_update(
4489
            api_get_course_info(),
4490
            TOOL_LEARNPATH_CATEGORY,
4491
            $id,
4492
            $action,
4493
            api_get_user_id()
4494
        );
4495
    }
4496
4497
    /**
4498
     * Publishes a learnpath. This basically means show or hide the learnpath
4499
     * on the course homepage
4500
     * Can be used as abstract.
4501
     *
4502
     * @param int    $lp_id          Learnpath id
4503
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4504
     *
4505
     * @return bool
4506
     */
4507
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4508
    {
4509
        $course_id = api_get_course_int_id();
4510
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4511
        $lp_id = (int) $lp_id;
4512
        $sql = "SELECT * FROM $tbl_lp
4513
                WHERE iid = $lp_id";
4514
        $result = Database::query($sql);
4515
        if (Database::num_rows($result)) {
4516
            $row = Database::fetch_array($result);
4517
            $name = Database::escape_string($row['name']);
4518
            if ($set_visibility == 'i') {
4519
                $v = 0;
4520
            }
4521
            if ($set_visibility == 'v') {
4522
                $v = 1;
4523
            }
4524
4525
            $session_id = api_get_session_id();
4526
            $session_condition = api_get_session_condition($session_id);
4527
4528
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4529
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4530
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4531
4532
            $sql = "SELECT * FROM $tbl_tool
4533
                    WHERE
4534
                        c_id = $course_id AND
4535
                        (link = '$link' OR link = '$oldLink') AND
4536
                        image = 'scormbuilder.gif' AND
4537
                        (
4538
                            link LIKE '$link%' OR
4539
                            link LIKE '$oldLink%'
4540
                        )
4541
                        $session_condition
4542
                    ";
4543
4544
            $result = Database::query($sql);
4545
            $num = Database::num_rows($result);
4546
            if ($set_visibility == 'i' && $num > 0) {
4547
                $sql = "DELETE FROM $tbl_tool
4548
                        WHERE 
4549
                            c_id = $course_id AND 
4550
                            (link = '$link' OR link = '$oldLink') AND 
4551
                            image='scormbuilder.gif' 
4552
                            $session_condition";
4553
                Database::query($sql);
4554
            } elseif ($set_visibility == 'v' && $num == 0) {
4555
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4556
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4557
                Database::query($sql);
4558
                $insertId = Database::insert_id();
4559
                if ($insertId) {
4560
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4561
                    Database::query($sql);
4562
                }
4563
            } elseif ($set_visibility == 'v' && $num > 0) {
4564
                $sql = "UPDATE $tbl_tool SET
4565
                            c_id = $course_id,
4566
                            name = '$name',
4567
                            link = '$link',
4568
                            image = 'scormbuilder.gif',
4569
                            visibility = '$v',
4570
                            admin = '0',
4571
                            address = 'pastillegris.gif',
4572
                            added_tool = 0,
4573
                            session_id = $session_id
4574
                        WHERE
4575
                            c_id = ".$course_id." AND
4576
                            (link = '$link' OR link = '$oldLink') AND 
4577
                            image='scormbuilder.gif' 
4578
                            $session_condition
4579
                        ";
4580
                Database::query($sql);
4581
            } else {
4582
                // Parameter and database incompatible, do nothing, exit.
4583
                return false;
4584
            }
4585
        } else {
4586
            return false;
4587
        }
4588
    }
4589
4590
    /**
4591
     * Publishes a learnpath.
4592
     * Show or hide the learnpath category on the course homepage.
4593
     *
4594
     * @param int $id
4595
     * @param int $setVisibility
4596
     *
4597
     * @throws \Doctrine\ORM\NonUniqueResultException
4598
     * @throws \Doctrine\ORM\ORMException
4599
     * @throws \Doctrine\ORM\OptimisticLockException
4600
     * @throws \Doctrine\ORM\TransactionRequiredException
4601
     *
4602
     * @return bool
4603
     */
4604
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4605
    {
4606
        $courseId = api_get_course_int_id();
4607
        $sessionId = api_get_session_id();
4608
        $sessionCondition = api_get_session_condition(
4609
            $sessionId,
4610
            true,
4611
            false,
4612
            't.sessionId'
4613
        );
4614
4615
        $em = Database::getManager();
4616
4617
        /** @var CLpCategory $category */
4618
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4619
4620
        if (!$category) {
4621
            return false;
4622
        }
4623
4624
        $link = self::getCategoryLinkForTool($id);
4625
4626
        /** @var CTool $tool */
4627
        $tool = $em->createQuery("
4628
                SELECT t FROM ChamiloCourseBundle:CTool t
4629
                WHERE
4630
                    t.cId = :course AND
4631
                    t.link = :link1 AND
4632
                    t.image = 'lp_category.gif' AND
4633
                    t.link LIKE :link2
4634
                    $sessionCondition
4635
            ")
4636
            ->setParameters([
4637
                'course' => (int) $courseId,
4638
                'link1' => $link,
4639
                'link2' => "$link%",
4640
            ])
4641
            ->getOneOrNullResult();
4642
4643
        if ($setVisibility == 0 && $tool) {
4644
            $em->remove($tool);
4645
            $em->flush();
4646
4647
            return true;
4648
        }
4649
4650
        if ($setVisibility == 1 && !$tool) {
4651
            $tool = new CTool();
4652
            $tool
4653
                ->setCategory('authoring')
4654
                ->setCId($courseId)
4655
                ->setName(strip_tags($category->getName()))
4656
                ->setLink($link)
4657
                ->setImage('lp_category.gif')
4658
                ->setVisibility(1)
4659
                ->setAdmin(0)
4660
                ->setAddress('pastillegris.gif')
4661
                ->setAddedTool(0)
4662
                ->setSessionId($sessionId)
4663
                ->setTarget('_self');
4664
4665
            $em->persist($tool);
4666
            $em->flush();
4667
4668
            $tool->setId($tool->getIid());
4669
4670
            $em->persist($tool);
4671
            $em->flush();
4672
4673
            return true;
4674
        }
4675
4676
        if ($setVisibility == 1 && $tool) {
4677
            $tool
4678
                ->setName(strip_tags($category->getName()))
4679
                ->setVisibility(1);
4680
4681
            $em->persist($tool);
4682
            $em->flush();
4683
4684
            return true;
4685
        }
4686
4687
        return false;
4688
    }
4689
4690
    /**
4691
     * Check if the learnpath category is visible for a user.
4692
     *
4693
     * @param CLpCategory $category
4694
     * @param User        $user
4695
     *
4696
     * @return bool
4697
     */
4698
    public static function categoryIsVisibleForStudent(
4699
        CLpCategory $category,
4700
        User $user
4701
    ) {
4702
        $subscriptionSettings = learnpath::getSubscriptionSettings();
4703
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4704
            return true;
4705
        }
4706
4707
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4708
4709
        if ($isAllowedToEdit) {
4710
            return true;
4711
        }
4712
4713
        if (empty($category)) {
4714
            return false;
4715
        }
4716
4717
        $users = $category->getUsers();
4718
4719
        if (empty($users) || !$users->count()) {
4720
            return true;
4721
        }
4722
4723
        if ($category->hasUserAdded($user)) {
4724
            return true;
4725
        }
4726
4727
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4728
        if (!empty($groups)) {
4729
            $em = Database::getManager();
4730
4731
            /** @var ItemPropertyRepository $itemRepo */
4732
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4733
4734
            /** @var CourseRepository $courseRepo */
4735
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4736
            $sessionId = api_get_session_id();
4737
            $session = null;
4738
            if (!empty($sessionId)) {
4739
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4740
            }
4741
4742
            $course = $courseRepo->find(api_get_course_int_id());
4743
4744
            // Subscribed groups to a LP
4745
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4746
                TOOL_LEARNPATH_CATEGORY,
4747
                $category->getId(),
4748
                $course,
4749
                $session
4750
            );
4751
4752
            if (!empty($subscribedGroupsInLp)) {
4753
                $groups = array_column($groups, 'iid');
4754
                /** @var CItemProperty $item */
4755
                foreach ($subscribedGroupsInLp as $item) {
4756
                    if ($item->getGroup() &&
4757
                        in_array($item->getGroup()->getId(), $groups)
4758
                    ) {
4759
                        return true;
4760
                    }
4761
                }
4762
            }
4763
        }
4764
4765
        return false;
4766
    }
4767
4768
    /**
4769
     * Check if a learnpath category is published as course tool.
4770
     *
4771
     * @param CLpCategory $category
4772
     * @param int         $courseId
4773
     *
4774
     * @return bool
4775
     */
4776
    public static function categoryIsPublished(
4777
        CLpCategory $category,
4778
        $courseId
4779
    ) {
4780
        $link = self::getCategoryLinkForTool($category->getId());
4781
        $em = Database::getManager();
4782
4783
        $tools = $em
4784
            ->createQuery("
4785
                SELECT t FROM ChamiloCourseBundle:CTool t
4786
                WHERE t.cId = :course AND 
4787
                    t.name = :name AND
4788
                    t.image = 'lp_category.gif' AND
4789
                    t.link LIKE :link
4790
            ")
4791
            ->setParameters([
4792
                'course' => $courseId,
4793
                'name' => strip_tags($category->getName()),
4794
                'link' => "$link%",
4795
            ])
4796
            ->getResult();
4797
4798
        /** @var CTool $tool */
4799
        $tool = current($tools);
4800
4801
        return $tool ? $tool->getVisibility() : false;
4802
    }
4803
4804
    /**
4805
     * Restart the whole learnpath. Return the URL of the first element.
4806
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4807
     * To use a similar method  statically, use the create_new_attempt() method.
4808
     *
4809
     * @return bool
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 $enc New encoding
5050
     *
5051
     * @return bool
5052
     *
5053
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
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 $lib Proximity setting
5085
     *
5086
     * @return bool True on update success. False otherwise.
5087
     */
5088
    public function set_jslib($lib = '')
5089
    {
5090
        if ($this->debug > 0) {
5091
            error_log('In learnpath::set_jslib()', 0);
5092
        }
5093
        $lp = $this->get_id();
5094
        $course_id = api_get_course_int_id();
5095
5096
        if ($lp != 0) {
5097
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5098
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5099
                    WHERE iid = $lp";
5100
            $res = Database::query($sql);
5101
5102
            return $res;
5103
        } else {
5104
            return false;
5105
        }
5106
    }
5107
5108
    /**
5109
     * Sets the name of the LP maker (publisher) (and save).
5110
     *
5111
     * @param string $name Optional string giving the new content_maker of this learnpath
5112
     *
5113
     * @return bool True
5114
     */
5115
    public function set_maker($name = '')
5116
    {
5117
        if ($this->debug > 0) {
5118
            error_log('In learnpath::set_maker()', 0);
5119
        }
5120
        if (empty($name)) {
5121
            return false;
5122
        }
5123
        $this->maker = $name;
5124
        $table = Database::get_course_table(TABLE_LP_MAIN);
5125
        $lp_id = $this->get_id();
5126
        $sql = "UPDATE $table SET
5127
                content_maker = '".Database::escape_string($this->maker)."'
5128
                WHERE iid = $lp_id";
5129
        if ($this->debug > 2) {
5130
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5131
        }
5132
        Database::query($sql);
5133
5134
        return true;
5135
    }
5136
5137
    /**
5138
     * Sets the name of the current learnpath (and save).
5139
     *
5140
     * @param string $name Optional string giving the new name of this learnpath
5141
     *
5142
     * @return bool True/False
5143
     */
5144
    public function set_name($name = null)
5145
    {
5146
        if ($this->debug > 0) {
5147
            error_log('In learnpath::set_name()', 0);
5148
        }
5149
        if (empty($name)) {
5150
            return false;
5151
        }
5152
        $this->name = Database::escape_string($name);
5153
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5154
        $lp_id = $this->get_id();
5155
        $course_id = $this->course_info['real_id'];
5156
        $sql = "UPDATE $lp_table SET
5157
                name = '".Database::escape_string($this->name)."'
5158
                WHERE iid = $lp_id";
5159
        if ($this->debug > 2) {
5160
            error_log('lp updated with new name : '.$this->name, 0);
5161
        }
5162
        $result = Database::query($sql);
5163
        // If the lp is visible on the homepage, change his name there.
5164
        if (Database::affected_rows($result)) {
5165
            $session_id = api_get_session_id();
5166
            $session_condition = api_get_session_condition($session_id);
5167
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5168
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5169
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5170
            	    WHERE
5171
            	        c_id = $course_id AND
5172
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5173
            Database::query($sql);
5174
5175
            return true;
5176
        } else {
5177
            return false;
5178
        }
5179
    }
5180
5181
    /**
5182
     * Set index specified prefix terms for all items in this path.
5183
     *
5184
     * @param string $terms_string Comma-separated list of terms
5185
     * @param string $prefix       Xapian term prefix
5186
     *
5187
     * @return bool False on error, true otherwise
5188
     */
5189
    public function set_terms_by_prefix($terms_string, $prefix)
5190
    {
5191
        $course_id = api_get_course_int_id();
5192
        if (api_get_setting('search_enabled') !== 'true') {
5193
            return false;
5194
        }
5195
5196
        if (!extension_loaded('xapian')) {
5197
            return false;
5198
        }
5199
5200
        $terms_string = trim($terms_string);
5201
        $terms = explode(',', $terms_string);
5202
        array_walk($terms, 'trim_value');
5203
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5204
5205
        // Don't do anything if no change, verify only at DB, not the search engine.
5206
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5207
            return false;
5208
        }
5209
5210
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5211
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5212
5213
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5214
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5215
        $lp_id = intval($_POST['lp_id']);
5216
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5217
        $result = Database::query($sql);
5218
        $di = new ChamiloIndexer();
5219
5220
        while ($lp_item = Database::fetch_array($result)) {
5221
            // Get search_did.
5222
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5223
            $sql = 'SELECT * FROM %s
5224
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5225
                    LIMIT 1';
5226
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5227
5228
            //echo $sql; echo '<br>';
5229
            $res = Database::query($sql);
5230
            if (Database::num_rows($res) > 0) {
5231
                $se_ref = Database::fetch_array($res);
5232
                // Compare terms.
5233
                $doc = $di->get_document($se_ref['search_did']);
5234
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5235
                $xterms = [];
5236
                foreach ($xapian_terms as $xapian_term) {
5237
                    $xterms[] = substr($xapian_term['name'], 1);
5238
                }
5239
5240
                $dterms = $terms;
5241
                $missing_terms = array_diff($dterms, $xterms);
5242
                $deprecated_terms = array_diff($xterms, $dterms);
5243
5244
                // Save it to search engine.
5245
                foreach ($missing_terms as $term) {
5246
                    $doc->add_term($prefix.$term, 1);
5247
                }
5248
                foreach ($deprecated_terms as $term) {
5249
                    $doc->remove_term($prefix.$term);
5250
                }
5251
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5252
                $di->getDb()->flush();
5253
            }
5254
        }
5255
5256
        return true;
5257
    }
5258
5259
    /**
5260
     * Sets the theme of the LP (local/remote) (and save).
5261
     *
5262
     * @param string $name Optional string giving the new theme of this learnpath
5263
     *
5264
     * @return bool Returns true if theme name is not empty
5265
     */
5266
    public function set_theme($name = '')
5267
    {
5268
        if ($this->debug > 0) {
5269
            error_log('In learnpath::set_theme()', 0);
5270
        }
5271
        $this->theme = $name;
5272
        $table = Database::get_course_table(TABLE_LP_MAIN);
5273
        $lp_id = $this->get_id();
5274
        $sql = "UPDATE $table 
5275
                SET theme = '".Database::escape_string($this->theme)."'
5276
                WHERE iid = $lp_id";
5277
        if ($this->debug > 2) {
5278
            error_log('lp updated with new theme : '.$this->theme, 0);
5279
        }
5280
        Database::query($sql);
5281
5282
        return true;
5283
    }
5284
5285
    /**
5286
     * Sets the image of an LP (and save).
5287
     *
5288
     * @param string $name Optional string giving the new image of this learnpath
5289
     *
5290
     * @return bool Returns true if theme name is not empty
5291
     */
5292
    public function set_preview_image($name = '')
5293
    {
5294
        if ($this->debug > 0) {
5295
            error_log('In learnpath::set_preview_image()', 0);
5296
        }
5297
5298
        $this->preview_image = $name;
5299
        $table = Database::get_course_table(TABLE_LP_MAIN);
5300
        $lp_id = $this->get_id();
5301
        $sql = "UPDATE $table SET
5302
                preview_image = '".Database::escape_string($this->preview_image)."'
5303
                WHERE iid = $lp_id";
5304
        if ($this->debug > 2) {
5305
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5306
        }
5307
        Database::query($sql);
5308
5309
        return true;
5310
    }
5311
5312
    /**
5313
     * Sets the author of a LP (and save).
5314
     *
5315
     * @param string $name Optional string giving the new author of this learnpath
5316
     *
5317
     * @return bool Returns true if author's name is not empty
5318
     */
5319
    public function set_author($name = '')
5320
    {
5321
        if ($this->debug > 0) {
5322
            error_log('In learnpath::set_author()', 0);
5323
        }
5324
        $this->author = $name;
5325
        $table = Database::get_course_table(TABLE_LP_MAIN);
5326
        $lp_id = $this->get_id();
5327
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5328
                WHERE iid = $lp_id";
5329
        if ($this->debug > 2) {
5330
            error_log('lp updated with new preview author : '.$this->author, 0);
5331
        }
5332
        Database::query($sql);
5333
5334
        return true;
5335
    }
5336
5337
    /**
5338
     * Sets the hide_toc_frame parameter of a LP (and save).
5339
     *
5340
     * @param int $hide 1 if frame is hidden 0 then else
5341
     *
5342
     * @return bool Returns true if author's name is not empty
5343
     */
5344
    public function set_hide_toc_frame($hide)
5345
    {
5346
        if ($this->debug > 0) {
5347
            error_log('In learnpath::set_hide_toc_frame()', 0);
5348
        }
5349
        if (intval($hide) == $hide) {
5350
            $this->hide_toc_frame = $hide;
5351
            $table = Database::get_course_table(TABLE_LP_MAIN);
5352
            $lp_id = $this->get_id();
5353
            $sql = "UPDATE $table SET
5354
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5355
                    WHERE iid = $lp_id";
5356
            if ($this->debug > 2) {
5357
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5358
            }
5359
            Database::query($sql);
5360
5361
            return true;
5362
        } else {
5363
            return false;
5364
        }
5365
    }
5366
5367
    /**
5368
     * Sets the prerequisite of a LP (and save).
5369
     *
5370
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5371
     *
5372
     * @return bool returns true if prerequisite is not empty
5373
     */
5374
    public function set_prerequisite($prerequisite)
5375
    {
5376
        if ($this->debug > 0) {
5377
            error_log('In learnpath::set_prerequisite()', 0);
5378
        }
5379
        $this->prerequisite = intval($prerequisite);
5380
        $table = Database::get_course_table(TABLE_LP_MAIN);
5381
        $lp_id = $this->get_id();
5382
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5383
                WHERE iid = $lp_id";
5384
        if ($this->debug > 2) {
5385
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5386
        }
5387
        Database::query($sql);
5388
5389
        return true;
5390
    }
5391
5392
    /**
5393
     * Sets the location/proximity of the LP (local/remote) (and save).
5394
     *
5395
     * @param string $name Optional string giving the new location of this learnpath
5396
     *
5397
     * @return bool True on success / False on error
5398
     */
5399
    public function set_proximity($name = '')
5400
    {
5401
        if ($this->debug > 0) {
5402
            error_log('In learnpath::set_proximity()', 0);
5403
        }
5404
        if (empty($name)) {
5405
            return false;
5406
        }
5407
5408
        $this->proximity = $name;
5409
        $table = Database::get_course_table(TABLE_LP_MAIN);
5410
        $lp_id = $this->get_id();
5411
        $sql = "UPDATE $table SET
5412
                    content_local = '".Database::escape_string($name)."'
5413
                WHERE iid = $lp_id";
5414
        if ($this->debug > 2) {
5415
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5416
        }
5417
        Database::query($sql);
5418
5419
        return true;
5420
    }
5421
5422
    /**
5423
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5424
     *
5425
     * @param int $id DB ID of the item
5426
     */
5427
    public function set_previous_item($id)
5428
    {
5429
        if ($this->debug > 0) {
5430
            error_log('In learnpath::set_previous_item()', 0);
5431
        }
5432
        $this->last = $id;
5433
    }
5434
5435
    /**
5436
     * Sets use_max_score.
5437
     *
5438
     * @param int $use_max_score Optional string giving the new location of this learnpath
5439
     *
5440
     * @return bool True on success / False on error
5441
     */
5442
    public function set_use_max_score($use_max_score = 1)
5443
    {
5444
        if ($this->debug > 0) {
5445
            error_log('In learnpath::set_use_max_score()', 0);
5446
        }
5447
        $use_max_score = intval($use_max_score);
5448
        $this->use_max_score = $use_max_score;
5449
        $table = Database::get_course_table(TABLE_LP_MAIN);
5450
        $lp_id = $this->get_id();
5451
        $sql = "UPDATE $table SET
5452
                    use_max_score = '".$this->use_max_score."'
5453
                WHERE iid = $lp_id";
5454
5455
        if ($this->debug > 2) {
5456
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5457
        }
5458
        Database::query($sql);
5459
5460
        return true;
5461
    }
5462
5463
    /**
5464
     * Sets and saves the expired_on date.
5465
     *
5466
     * @param string $expired_on Optional string giving the new author of this learnpath
5467
     *
5468
     * @throws \Doctrine\ORM\OptimisticLockException
5469
     *
5470
     * @return bool Returns true if author's name is not empty
5471
     */
5472
    public function set_expired_on($expired_on)
5473
    {
5474
        if ($this->debug > 0) {
5475
            error_log('In learnpath::set_expired_on()', 0);
5476
        }
5477
5478
        $em = Database::getManager();
5479
        /** @var CLp $lp */
5480
        $lp = $em
5481
            ->getRepository('ChamiloCourseBundle:CLp')
5482
            ->findOneBy(
5483
                [
5484
                    'iid' => $this->get_id(),
5485
                ]
5486
            );
5487
5488
        if (!$lp) {
5489
            return false;
5490
        }
5491
5492
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5493
5494
        $lp->setExpiredOn($this->expired_on);
5495
        $em->persist($lp);
5496
        $em->flush();
5497
5498
        if ($this->debug > 2) {
5499
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5500
        }
5501
5502
        return true;
5503
    }
5504
5505
    /**
5506
     * Sets and saves the publicated_on date.
5507
     *
5508
     * @param string $publicated_on Optional string giving the new author of this learnpath
5509
     *
5510
     * @throws \Doctrine\ORM\OptimisticLockException
5511
     *
5512
     * @return bool Returns true if author's name is not empty
5513
     */
5514
    public function set_publicated_on($publicated_on)
5515
    {
5516
        if ($this->debug > 0) {
5517
            error_log('In learnpath::set_expired_on()', 0);
5518
        }
5519
5520
        $em = Database::getManager();
5521
        /** @var CLp $lp */
5522
        $lp = $em
5523
            ->getRepository('ChamiloCourseBundle:CLp')
5524
            ->findOneBy(
5525
                [
5526
                    'iid' => $this->get_id(),
5527
                ]
5528
            );
5529
5530
        if (!$lp) {
5531
            return false;
5532
        }
5533
5534
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5535
        $lp->setPublicatedOn($this->publicated_on);
5536
        $em->persist($lp);
5537
        $em->flush();
5538
5539
        if ($this->debug > 2) {
5540
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5541
        }
5542
5543
        return true;
5544
    }
5545
5546
    /**
5547
     * Sets and saves the expired_on date.
5548
     *
5549
     * @return bool Returns true if author's name is not empty
5550
     */
5551
    public function set_modified_on()
5552
    {
5553
        if ($this->debug > 0) {
5554
            error_log('In learnpath::set_expired_on()', 0);
5555
        }
5556
        $this->modified_on = api_get_utc_datetime();
5557
        $table = Database::get_course_table(TABLE_LP_MAIN);
5558
        $lp_id = $this->get_id();
5559
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5560
                WHERE iid = $lp_id";
5561
        if ($this->debug > 2) {
5562
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5563
        }
5564
        Database::query($sql);
5565
5566
        return true;
5567
    }
5568
5569
    /**
5570
     * Sets the object's error message.
5571
     *
5572
     * @param string $error Error message. If empty, reinits the error string
5573
     */
5574
    public function set_error_msg($error = '')
5575
    {
5576
        if ($this->debug > 0) {
5577
            error_log('In learnpath::set_error_msg()', 0);
5578
        }
5579
        if (empty($error)) {
5580
            $this->error = '';
5581
        } else {
5582
            $this->error .= $error;
5583
        }
5584
    }
5585
5586
    /**
5587
     * Launches the current item if not 'sco'
5588
     * (starts timer and make sure there is a record ready in the DB).
5589
     *
5590
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5591
     *
5592
     * @return bool
5593
     */
5594
    public function start_current_item($allow_new_attempt = false)
5595
    {
5596
        $debug = $this->debug;
5597
        if ($debug) {
5598
            error_log('In learnpath::start_current_item()');
5599
            error_log('current: '.$this->current);
5600
        }
5601
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5602
            $type = $this->get_type();
5603
            $item_type = $this->items[$this->current]->get_type();
5604
            if (($type == 2 && $item_type != 'sco') ||
5605
                ($type == 3 && $item_type != 'au') ||
5606
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5607
            ) {
5608
                if ($debug) {
5609
                    error_log('item type: '.$item_type);
5610
                    error_log('lp type: '.$type);
5611
                }
5612
                $this->items[$this->current]->open($allow_new_attempt);
5613
                $this->autocomplete_parents($this->current);
5614
                $prereq_check = $this->prerequisites_match($this->current);
5615
                if ($debug) {
5616
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5617
                }
5618
                $this->items[$this->current]->save(false, $prereq_check);
5619
            }
5620
            // If sco, then it is supposed to have been updated by some other call.
5621
            if ($item_type == 'sco') {
5622
                $this->items[$this->current]->restart();
5623
            }
5624
        }
5625
        if ($debug) {
5626
            error_log('lp_view_session_id');
5627
            error_log($this->lp_view_session_id);
5628
            error_log('api session id');
5629
            error_log(api_get_session_id());
5630
            error_log('End of learnpath::start_current_item()');
5631
        }
5632
5633
        return true;
5634
    }
5635
5636
    /**
5637
     * Stops the processing and counters for the old item (as held in $this->last).
5638
     *
5639
     * @return bool True/False
5640
     */
5641
    public function stop_previous_item()
5642
    {
5643
        $debug = $this->debug;
5644
        if ($debug) {
5645
            error_log('In learnpath::stop_previous_item()', 0);
5646
        }
5647
5648
        if ($this->last != 0 && $this->last != $this->current &&
5649
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5650
        ) {
5651
            if ($debug) {
5652
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5653
            }
5654
            switch ($this->get_type()) {
5655
                case '3':
5656
                    if ($this->items[$this->last]->get_type() != 'au') {
5657
                        if ($debug) {
5658
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5659
                        }
5660
                        $this->items[$this->last]->close();
5661
                    } else {
5662
                        if ($debug) {
5663
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5664
                        }
5665
                    }
5666
                    break;
5667
                case '2':
5668
                    if ($this->items[$this->last]->get_type() != 'sco') {
5669
                        if ($debug) {
5670
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5671
                        }
5672
                        $this->items[$this->last]->close();
5673
                    } else {
5674
                        if ($debug) {
5675
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5676
                        }
5677
                    }
5678
                    break;
5679
                case '1':
5680
                default:
5681
                    if ($debug) {
5682
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5683
                    }
5684
                    $this->items[$this->last]->close();
5685
                    break;
5686
            }
5687
        } else {
5688
            if ($debug) {
5689
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5690
            }
5691
5692
            return false;
5693
        }
5694
5695
        return true;
5696
    }
5697
5698
    /**
5699
     * Updates the default view mode from fullscreen to embedded and inversely.
5700
     *
5701
     * @return string The current default view mode ('fullscreen' or 'embedded')
5702
     */
5703
    public function update_default_view_mode()
5704
    {
5705
        if ($this->debug > 0) {
5706
            error_log('In learnpath::update_default_view_mode()', 0);
5707
        }
5708
        $table = Database::get_course_table(TABLE_LP_MAIN);
5709
        $sql = "SELECT * FROM $table
5710
                WHERE iid = ".$this->get_id();
5711
        $res = Database::query($sql);
5712
        if (Database::num_rows($res) > 0) {
5713
            $row = Database::fetch_array($res);
5714
            $default_view_mode = $row['default_view_mod'];
5715
            $view_mode = $default_view_mode;
5716
            switch ($default_view_mode) {
5717
                case 'fullscreen': // default with popup
5718
                    $view_mode = 'embedded';
5719
                    break;
5720
                case 'embedded': // default view with left menu
5721
                    $view_mode = 'embedframe';
5722
                    break;
5723
                case 'embedframe': //folded menu
5724
                    $view_mode = 'impress';
5725
                    break;
5726
                case 'impress':
5727
                    $view_mode = 'fullscreen';
5728
                    break;
5729
            }
5730
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5731
                    WHERE iid = ".$this->get_id();
5732
            Database::query($sql);
5733
            $this->mode = $view_mode;
5734
5735
            return $view_mode;
5736
        } else {
5737
            if ($this->debug > 2) {
5738
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5739
            }
5740
        }
5741
5742
        return -1;
5743
    }
5744
5745
    /**
5746
     * Updates the default behaviour about auto-commiting SCORM updates.
5747
     *
5748
     * @return bool True if auto-commit has been set to 'on', false otherwise
5749
     */
5750
    public function update_default_scorm_commit()
5751
    {
5752
        if ($this->debug > 0) {
5753
            error_log('In learnpath::update_default_scorm_commit()', 0);
5754
        }
5755
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5756
        $sql = "SELECT * FROM $lp_table
5757
                WHERE iid = ".$this->get_id();
5758
        $res = Database::query($sql);
5759
        if (Database::num_rows($res) > 0) {
5760
            $row = Database::fetch_array($res);
5761
            $force = $row['force_commit'];
5762
            if ($force == 1) {
5763
                $force = 0;
5764
                $force_return = false;
5765
            } elseif ($force == 0) {
5766
                $force = 1;
5767
                $force_return = true;
5768
            }
5769
            $sql = "UPDATE $lp_table SET force_commit = $force
5770
                    WHERE iid = ".$this->get_id();
5771
            Database::query($sql);
5772
            $this->force_commit = $force_return;
5773
5774
            return $force_return;
5775
        } else {
5776
            if ($this->debug > 2) {
5777
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5778
            }
5779
        }
5780
5781
        return -1;
5782
    }
5783
5784
    /**
5785
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5786
     *
5787
     * @return bool True on success, false on failure
5788
     */
5789
    public function update_display_order()
5790
    {
5791
        $course_id = api_get_course_int_id();
5792
        $table = Database::get_course_table(TABLE_LP_MAIN);
5793
        $sql = "SELECT * FROM $table 
5794
                WHERE c_id = $course_id ORDER BY display_order";
5795
        $res = Database::query($sql);
5796
        if ($res === false) {
5797
            return false;
5798
        }
5799
5800
        $num = Database::num_rows($res);
5801
        // First check the order is correct, globally (might be wrong because
5802
        // of versions < 1.8.4).
5803
        if ($num > 0) {
5804
            $i = 1;
5805
            while ($row = Database::fetch_array($res)) {
5806
                if ($row['display_order'] != $i) {
5807
                    // If we find a gap in the order, we need to fix it.
5808
                    $sql = "UPDATE $table SET display_order = $i
5809
                            WHERE iid = ".$row['iid'];
5810
                    Database::query($sql);
5811
                }
5812
                $i++;
5813
            }
5814
        }
5815
5816
        return true;
5817
    }
5818
5819
    /**
5820
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5821
     *
5822
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5823
     */
5824
    public function update_reinit()
5825
    {
5826
        $course_id = api_get_course_int_id();
5827
        if ($this->debug > 0) {
5828
            error_log('In learnpath::update_reinit()', 0);
5829
        }
5830
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5831
        $sql = "SELECT * FROM $lp_table
5832
                WHERE iid = ".$this->get_id();
5833
        $res = Database::query($sql);
5834
        if (Database::num_rows($res) > 0) {
5835
            $row = Database::fetch_array($res);
5836
            $force = $row['prevent_reinit'];
5837
            if ($force == 1) {
5838
                $force = 0;
5839
            } elseif ($force == 0) {
5840
                $force = 1;
5841
            }
5842
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5843
                    WHERE iid = ".$this->get_id();
5844
            Database::query($sql);
5845
            $this->prevent_reinit = $force;
5846
5847
            return $force;
5848
        } else {
5849
            if ($this->debug > 2) {
5850
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5851
            }
5852
        }
5853
5854
        return -1;
5855
    }
5856
5857
    /**
5858
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5859
     *
5860
     * @return string 'single', 'multi' or 'seriousgame'
5861
     *
5862
     * @author ndiechburg <[email protected]>
5863
     */
5864
    public function get_attempt_mode()
5865
    {
5866
        //Set default value for seriousgame_mode
5867
        if (!isset($this->seriousgame_mode)) {
5868
            $this->seriousgame_mode = 0;
5869
        }
5870
        // Set default value for prevent_reinit
5871
        if (!isset($this->prevent_reinit)) {
5872
            $this->prevent_reinit = 1;
5873
        }
5874
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5875
            return 'seriousgame';
5876
        }
5877
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5878
            return 'single';
5879
        }
5880
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5881
            return 'multiple';
5882
        }
5883
5884
        return 'single';
5885
    }
5886
5887
    /**
5888
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5889
     *
5890
     * @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...
5891
     *
5892
     * @return bool
5893
     *
5894
     * @author ndiechburg <[email protected]>
5895
     */
5896
    public function set_attempt_mode($mode)
5897
    {
5898
        switch ($mode) {
5899
            case 'seriousgame':
5900
                $sg_mode = 1;
5901
                $prevent_reinit = 1;
5902
                break;
5903
            case 'single':
5904
                $sg_mode = 0;
5905
                $prevent_reinit = 1;
5906
                break;
5907
            case 'multiple':
5908
                $sg_mode = 0;
5909
                $prevent_reinit = 0;
5910
                break;
5911
            default:
5912
                $sg_mode = 0;
5913
                $prevent_reinit = 0;
5914
                break;
5915
        }
5916
        $this->prevent_reinit = $prevent_reinit;
5917
        $this->seriousgame_mode = $sg_mode;
5918
        $table = Database::get_course_table(TABLE_LP_MAIN);
5919
        $sql = "UPDATE $table SET
5920
                prevent_reinit = $prevent_reinit ,
5921
                seriousgame_mode = $sg_mode
5922
                WHERE iid = ".$this->get_id();
5923
        $res = Database::query($sql);
5924
        if ($res) {
5925
            return true;
5926
        } else {
5927
            return false;
5928
        }
5929
    }
5930
5931
    /**
5932
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5933
     *
5934
     * @author ndiechburg <[email protected]>
5935
     */
5936
    public function switch_attempt_mode()
5937
    {
5938
        if ($this->debug > 0) {
5939
            error_log('In learnpath::switch_attempt_mode()', 0);
5940
        }
5941
        $mode = $this->get_attempt_mode();
5942
        switch ($mode) {
5943
            case 'single':
5944
                $next_mode = 'multiple';
5945
                break;
5946
            case 'multiple':
5947
                $next_mode = 'seriousgame';
5948
                break;
5949
            case 'seriousgame':
5950
                $next_mode = 'single';
5951
                break;
5952
            default:
5953
                $next_mode = 'single';
5954
                break;
5955
        }
5956
        $this->set_attempt_mode($next_mode);
5957
    }
5958
5959
    /**
5960
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5961
     * but possibility to do again a completed item.
5962
     *
5963
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5964
     *
5965
     * @author ndiechburg <[email protected]>
5966
     */
5967
    public function set_seriousgame_mode()
5968
    {
5969
        if ($this->debug > 0) {
5970
            error_log('In learnpath::set_seriousgame_mode()', 0);
5971
        }
5972
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5973
        $sql = "SELECT * FROM $lp_table 
5974
                WHERE iid = ".$this->get_id();
5975
        $res = Database::query($sql);
5976
        if (Database::num_rows($res) > 0) {
5977
            $row = Database::fetch_array($res);
5978
            $force = $row['seriousgame_mode'];
5979
            if ($force == 1) {
5980
                $force = 0;
5981
            } elseif ($force == 0) {
5982
                $force = 1;
5983
            }
5984
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5985
			        WHERE iid = ".$this->get_id();
5986
            Database::query($sql);
5987
            $this->seriousgame_mode = $force;
5988
5989
            return $force;
5990
        } else {
5991
            if ($this->debug > 2) {
5992
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
5993
            }
5994
        }
5995
5996
        return -1;
5997
    }
5998
5999
    /**
6000
     * Updates the "scorm_debug" value that shows or hide the debug window.
6001
     *
6002
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6003
     */
6004
    public function update_scorm_debug()
6005
    {
6006
        if ($this->debug > 0) {
6007
            error_log('In learnpath::update_scorm_debug()', 0);
6008
        }
6009
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6010
        $sql = "SELECT * FROM $lp_table
6011
                WHERE iid = ".$this->get_id();
6012
        $res = Database::query($sql);
6013
        if (Database::num_rows($res) > 0) {
6014
            $row = Database::fetch_array($res);
6015
            $force = $row['debug'];
6016
            if ($force == 1) {
6017
                $force = 0;
6018
            } elseif ($force == 0) {
6019
                $force = 1;
6020
            }
6021
            $sql = "UPDATE $lp_table SET debug = $force
6022
                    WHERE iid = ".$this->get_id();
6023
            Database::query($sql);
6024
            $this->scorm_debug = $force;
6025
6026
            return $force;
6027
        } else {
6028
            if ($this->debug > 2) {
6029
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6030
            }
6031
        }
6032
6033
        return -1;
6034
    }
6035
6036
    /**
6037
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6038
     *
6039
     * @author Kevin Van Den Haute
6040
     *
6041
     * @param  array
6042
     */
6043
    public function tree_array($array)
6044
    {
6045
        if ($this->debug > 1) {
6046
            error_log('In learnpath::tree_array()', 0);
6047
        }
6048
        $array = $this->sort_tree_array($array);
6049
        $this->create_tree_array($array);
6050
    }
6051
6052
    /**
6053
     * Creates an array with the elements of the learning path tree in it.
6054
     *
6055
     * @author Kevin Van Den Haute
6056
     *
6057
     * @param array $array
6058
     * @param int   $parent
6059
     * @param int   $depth
6060
     * @param array $tmp
6061
     */
6062
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6063
    {
6064
        if ($this->debug > 1) {
6065
            error_log('In learnpath::create_tree_array())', 0);
6066
        }
6067
6068
        if (is_array($array)) {
6069
            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...
6070
                if ($array[$i]['parent_item_id'] == $parent) {
6071
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6072
                        $tmp[] = $array[$i]['parent_item_id'];
6073
                        $depth++;
6074
                    }
6075
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6076
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6077
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6078
6079
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6080
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6081
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6082
                    $this->arrMenu[] = [
6083
                        'id' => $array[$i]['id'],
6084
                        'ref' => $ref,
6085
                        'item_type' => $array[$i]['item_type'],
6086
                        'title' => $array[$i]['title'],
6087
                        'path' => $path,
6088
                        'description' => $array[$i]['description'],
6089
                        'parent_item_id' => $array[$i]['parent_item_id'],
6090
                        'previous_item_id' => $array[$i]['previous_item_id'],
6091
                        'next_item_id' => $array[$i]['next_item_id'],
6092
                        'min_score' => $array[$i]['min_score'],
6093
                        'max_score' => $array[$i]['max_score'],
6094
                        'mastery_score' => $array[$i]['mastery_score'],
6095
                        'display_order' => $array[$i]['display_order'],
6096
                        'prerequisite' => $preq,
6097
                        'depth' => $depth,
6098
                        'audio' => $audio,
6099
                        'prerequisite_min_score' => $prerequisiteMinScore,
6100
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6101
                    ];
6102
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6103
                }
6104
            }
6105
        }
6106
    }
6107
6108
    /**
6109
     * Sorts a multi dimensional array by parent id and display order.
6110
     *
6111
     * @author Kevin Van Den Haute
6112
     *
6113
     * @param array $array (array with al the learning path items in it)
6114
     *
6115
     * @return array
6116
     */
6117
    public function sort_tree_array($array)
6118
    {
6119
        foreach ($array as $key => $row) {
6120
            $parent[$key] = $row['parent_item_id'];
6121
            $position[$key] = $row['display_order'];
6122
        }
6123
6124
        if (count($array) > 0) {
6125
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6126
        }
6127
6128
        return $array;
6129
    }
6130
6131
    /**
6132
     * Function that creates a html list of learning path items so that we can add audio files to them.
6133
     *
6134
     * @author Kevin Van Den Haute
6135
     *
6136
     * @return string
6137
     */
6138
    public function overview()
6139
    {
6140
        if ($this->debug > 0) {
6141
            error_log('In learnpath::overview()', 0);
6142
        }
6143
6144
        $return = '';
6145
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6146
6147
        // we need to start a form when we want to update all the mp3 files
6148
        if ($update_audio == 'true') {
6149
            $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">';
6150
        }
6151
        $return .= '<div id="message"></div>';
6152
        if (count($this->items) == 0) {
6153
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6154
        } else {
6155
            $return_audio = '<table class="data_table">';
6156
            $return_audio .= '<tr>';
6157
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6158
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6159
            $return_audio .= '</tr>';
6160
6161
            if ($update_audio != 'true') {
6162
                $return .= '<div class="col-md-12">';
6163
                $return .= self::return_new_tree($update_audio);
6164
                $return .= '</div>';
6165
                $return .= Display::div(
6166
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6167
                    ['style' => 'float:left; margin-top:15px;width:100%']
6168
                );
6169
            } else {
6170
                $return_audio .= self::return_new_tree($update_audio);
6171
                $return .= $return_audio.'</table>';
6172
            }
6173
6174
            // We need to close the form when we are updating the mp3 files.
6175
            if ($update_audio == 'true') {
6176
                $return .= '<div class="footer-audio">';
6177
                $return .= Display::button(
6178
                    'save_audio',
6179
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6180
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6181
                );
6182
                $return .= '</div>';
6183
            }
6184
        }
6185
6186
        // We need to close the form when we are updating the mp3 files.
6187
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6188
            $return .= '</form>';
6189
        }
6190
6191
        return $return;
6192
    }
6193
6194
    /**
6195
     * @param string $update_audio
6196
     * @param bool   $drop_element_here
6197
     *
6198
     * @return string
6199
     */
6200
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6201
    {
6202
        $return = '';
6203
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6204
        $course_id = api_get_course_int_id();
6205
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6206
6207
        $sql = "SELECT * FROM $tbl_lp_item
6208
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6209
6210
        $result = Database::query($sql);
6211
        $arrLP = [];
6212
        while ($row = Database::fetch_array($result)) {
6213
            $arrLP[] = [
6214
                'id' => $row['iid'],
6215
                'item_type' => $row['item_type'],
6216
                'title' => Security::remove_XSS($row['title']),
6217
                'path' => $row['path'],
6218
                'description' => Security::remove_XSS($row['description']),
6219
                'parent_item_id' => $row['parent_item_id'],
6220
                'previous_item_id' => $row['previous_item_id'],
6221
                'next_item_id' => $row['next_item_id'],
6222
                'max_score' => $row['max_score'],
6223
                'min_score' => $row['min_score'],
6224
                'mastery_score' => $row['mastery_score'],
6225
                'prerequisite' => $row['prerequisite'],
6226
                'display_order' => $row['display_order'],
6227
                'audio' => $row['audio'],
6228
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6229
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6230
            ];
6231
        }
6232
6233
        $this->tree_array($arrLP);
6234
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6235
        unset($this->arrMenu);
6236
        $default_data = null;
6237
        $default_content = null;
6238
        $elements = [];
6239
        $return_audio = null;
6240
6241
        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...
6242
            $title = $arrLP[$i]['title'];
6243
            $title_cut = cut($arrLP[$i]['title'], 25);
6244
6245
            // Link for the documents
6246
            if ($arrLP[$i]['item_type'] == 'document') {
6247
                $url = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6248
                $title_cut = Display::url(
6249
                    $title_cut,
6250
                    $url,
6251
                    [
6252
                        'class' => 'ajax moved',
6253
                        'data-title' => $title,
6254
                        'title' => $title,
6255
                    ]
6256
                );
6257
            }
6258
6259
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6260
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6261
                Session::write('pathItem', $arrLP[$i]['path']);
6262
            }
6263
6264
            if (($i % 2) == 0) {
6265
                $oddClass = 'row_odd';
6266
            } else {
6267
                $oddClass = 'row_even';
6268
            }
6269
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6270
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6271
6272
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
6273
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6274
            } else {
6275
                if (file_exists('../img/lp_'.$icon_name.'.gif')) {
6276
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6277
                } else {
6278
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6279
                        $icon = Display::return_icon('certificate.png');
6280
                    } else {
6281
                        $icon = Display::return_icon('folder_document.gif');
6282
                    }
6283
                }
6284
            }
6285
6286
            // The audio column.
6287
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6288
            $audio = '';
6289
            if (!$update_audio || $update_audio != 'true') {
6290
                if (empty($arrLP[$i]['audio'])) {
6291
                    $audio .= '';
6292
                }
6293
            } else {
6294
                $types = self::getChapterTypes();
6295
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6296
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6297
                    if (!empty($arrLP[$i]['audio'])) {
6298
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6299
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6300
                    }
6301
                }
6302
            }
6303
6304
            $return_audio .= Display::span($icon.' '.$title).
6305
            Display::tag(
6306
                'td',
6307
                $audio,
6308
                ['style' => '']
6309
            );
6310
            $return_audio .= '</td>';
6311
            $move_icon = '';
6312
            $move_item_icon = '';
6313
            $edit_icon = '';
6314
            $delete_icon = '';
6315
            $audio_icon = '';
6316
            $prerequisities_icon = '';
6317
            $forumIcon = '';
6318
            $previewIcon = '';
6319
6320
            if ($is_allowed_to_edit) {
6321
                if (!$update_audio || $update_audio != 'true') {
6322
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6323
                        $move_icon .= '<a class="moved" href="#">';
6324
                        $move_icon .= Display::return_icon(
6325
                            'move_everywhere.png',
6326
                            get_lang('Move'),
6327
                            [],
6328
                            ICON_SIZE_TINY
6329
                        );
6330
                        $move_icon .= '</a>';
6331
                    }
6332
                }
6333
6334
                // No edit for this item types
6335
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6336
                    if ($arrLP[$i]['item_type'] != 'dir') {
6337
                        $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">';
6338
                        $edit_icon .= Display::return_icon(
6339
                            'edit.png',
6340
                            get_lang('LearnpathEditModule'),
6341
                            [],
6342
                            ICON_SIZE_TINY
6343
                        );
6344
                        $edit_icon .= '</a>';
6345
6346
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6347
                            $forumThread = null;
6348
                            if (isset($this->items[$arrLP[$i]['id']])) {
6349
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6350
                                    $this->course_int_id,
6351
                                    $this->lp_session_id
6352
                                );
6353
                            }
6354
                            if ($forumThread) {
6355
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6356
                                    'action' => 'dissociate_forum',
6357
                                    'id' => $arrLP[$i]['id'],
6358
                                    'lp_id' => $this->lp_id,
6359
                                ]);
6360
                                $forumIcon = Display::url(
6361
                                    Display::return_icon(
6362
                                        'forum.png',
6363
                                        get_lang('DissociateForumToLPItem'),
6364
                                        [],
6365
                                        ICON_SIZE_TINY
6366
                                    ),
6367
                                    $forumIconUrl,
6368
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6369
                                );
6370
                            } else {
6371
                                $forumIconUrl = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
6372
                                    'action' => 'create_forum',
6373
                                    'id' => $arrLP[$i]['id'],
6374
                                    'lp_id' => $this->lp_id,
6375
                                ]);
6376
                                $forumIcon = Display::url(
6377
                                    Display::return_icon(
6378
                                        'forum.png',
6379
                                        get_lang('AssociateForumToLPItem'),
6380
                                        [],
6381
                                        ICON_SIZE_TINY
6382
                                    ),
6383
                                    $forumIconUrl,
6384
                                    ['class' => "btn btn-default lp-btn-associate-forum"]
6385
                                );
6386
                            }
6387
                        }
6388
                    } else {
6389
                        $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">';
6390
                        $edit_icon .= Display::return_icon(
6391
                            'edit.png',
6392
                            get_lang('LearnpathEditModule'),
6393
                            [],
6394
                            ICON_SIZE_TINY
6395
                        );
6396
                        $edit_icon .= '</a>';
6397
                    }
6398
                } else {
6399
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6400
                        $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">';
6401
                        $edit_icon .= Display::return_icon(
6402
                            'edit.png',
6403
                            get_lang('Edit'),
6404
                            [],
6405
                            ICON_SIZE_TINY
6406
                        );
6407
                        $edit_icon .= '</a>';
6408
                    }
6409
                }
6410
6411
                $delete_icon .= ' <a 
6412
                    href="'.api_get_self().'?'.api_get_cidreq().'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6413
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6414
                    class="btn btn-default">';
6415
                $delete_icon .= Display::return_icon(
6416
                    'delete.png',
6417
                    get_lang('LearnpathDeleteModule'),
6418
                    [],
6419
                    ICON_SIZE_TINY
6420
                );
6421
                $delete_icon .= '</a>';
6422
6423
                $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6424
                $previewImage = Display::return_icon(
6425
                    'preview_view.png',
6426
                    get_lang('Preview'),
6427
                    [],
6428
                    ICON_SIZE_TINY
6429
                );
6430
6431
                switch ($arrLP[$i]['item_type']) {
6432
                    case TOOL_DOCUMENT:
6433
                    case TOOL_LP_FINAL_ITEM:
6434
                        $urlPreviewLink = api_get_self().'?'.api_get_cidreq().'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6435
                        $previewIcon = Display::url(
6436
                            $previewImage,
6437
                            $urlPreviewLink,
6438
                            [
6439
                                'target' => '_blank',
6440
                                'class' => 'btn btn-default',
6441
                                'data-title' => $arrLP[$i]['title'],
6442
                                'title' => $arrLP[$i]['title'],
6443
                            ]
6444
                        );
6445
                        break;
6446
                    case TOOL_THREAD:
6447
                    case TOOL_FORUM:
6448
                    case TOOL_QUIZ:
6449
                    case TOOL_STUDENTPUBLICATION:
6450
                    case TOOL_LP_FINAL_ITEM:
6451
                    case TOOL_LINK:
6452
                        //$target = '';
6453
                        //$class = 'btn btn-default ajax';
6454
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6455
                        $class = 'btn btn-default';
6456
                        $target = '_blank';
6457
                        //}
6458
6459
                        $link = self::rl_get_resource_link_for_learnpath(
6460
                            $this->course_int_id,
6461
                            $this->lp_id,
6462
                            $arrLP[$i]['id'],
6463
                            0,
6464
                            ''
6465
                        );
6466
                        $previewIcon = Display::url(
6467
                            $previewImage,
6468
                            $link,
6469
                            [
6470
                                'class' => $class,
6471
                                'data-title' => $arrLP[$i]['title'],
6472
                                'title' => $arrLP[$i]['title'],
6473
                                'target' => $target,
6474
                            ]
6475
                        );
6476
                        break;
6477
                    default:
6478
                        $previewIcon = Display::url(
6479
                            $previewImage,
6480
                            $url.'&action=view_item',
6481
                            ['class' => 'btn btn-default', 'target' => '_blank']
6482
                        );
6483
                        break;
6484
                }
6485
6486
                if ($arrLP[$i]['item_type'] != 'dir') {
6487
                    $prerequisities_icon = Display::url(
6488
                        Display::return_icon(
6489
                            'accept.png',
6490
                            get_lang('LearnpathPrerequisites'),
6491
                            [],
6492
                            ICON_SIZE_TINY
6493
                        ),
6494
                        $url.'&action=edit_item_prereq',
6495
                        ['class' => 'btn btn-default']
6496
                    );
6497
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6498
                        $move_item_icon = Display::url(
6499
                            Display::return_icon(
6500
                                'move.png',
6501
                                get_lang('Move'),
6502
                                [],
6503
                                ICON_SIZE_TINY
6504
                            ),
6505
                            $url.'&action=move_item',
6506
                            ['class' => 'btn btn-default']
6507
                        );
6508
                    }
6509
                    $audio_icon = Display::url(
6510
                        Display::return_icon(
6511
                            'audio.png',
6512
                            get_lang('UplUpload'),
6513
                            [],
6514
                            ICON_SIZE_TINY
6515
                        ),
6516
                        $url.'&action=add_audio',
6517
                        ['class' => 'btn btn-default']
6518
                    );
6519
                }
6520
            }
6521
            if ($update_audio != 'true') {
6522
                $row = $move_icon.' '.$icon.
6523
                    Display::span($title_cut).
6524
                    Display::tag(
6525
                        'div',
6526
                        "<div class=\"btn-group btn-group-xs\">$previewIcon $audio $edit_icon $forumIcon $prerequisities_icon $move_item_icon $audio_icon $delete_icon</div>",
6527
                        ['class' => 'btn-toolbar button_actions']
6528
                    );
6529
            } else {
6530
                $row = Display::span($title.$icon).Display::span($audio, ['class' => 'button_actions']);
6531
            }
6532
6533
            $parent_id = $arrLP[$i]['parent_item_id'];
6534
            $default_data[$arrLP[$i]['id']] = $row;
6535
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6536
6537
            if (empty($parent_id)) {
6538
                $elements[$arrLP[$i]['id']]['data'] = $row;
6539
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6540
            } else {
6541
                $parent_arrays = [];
6542
                if ($arrLP[$i]['depth'] > 1) {
6543
                    //Getting list of parents
6544
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6545
                        foreach ($arrLP as $item) {
6546
                            if ($item['id'] == $parent_id) {
6547
                                if ($item['parent_item_id'] == 0) {
6548
                                    $parent_id = $item['id'];
6549
                                    break;
6550
                                } else {
6551
                                    $parent_id = $item['parent_item_id'];
6552
                                    if (empty($parent_arrays)) {
6553
                                        $parent_arrays[] = intval($item['id']);
6554
                                    }
6555
                                    $parent_arrays[] = $parent_id;
6556
                                    break;
6557
                                }
6558
                            }
6559
                        }
6560
                    }
6561
                }
6562
6563
                if (!empty($parent_arrays)) {
6564
                    $parent_arrays = array_reverse($parent_arrays);
6565
                    $val = '$elements';
6566
                    $x = 0;
6567
                    foreach ($parent_arrays as $item) {
6568
                        if ($x != count($parent_arrays) - 1) {
6569
                            $val .= '["'.$item.'"]["children"]';
6570
                        } else {
6571
                            $val .= '["'.$item.'"]["children"]';
6572
                        }
6573
                        $x++;
6574
                    }
6575
                    $val .= "";
6576
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6577
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6578
                } else {
6579
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6580
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6581
                }
6582
            }
6583
        }
6584
6585
        $list = '<ul id="lp_item_list">';
6586
        $tree = self::print_recursive(
6587
            $elements,
6588
            $default_data,
6589
            $default_content
6590
        );
6591
6592
        if (!empty($tree)) {
6593
            $list .= $tree;
6594
        } else {
6595
            if ($drop_element_here) {
6596
                $list .= Display::return_message(get_lang("DragAndDropAnElementHere"));
6597
            }
6598
        }
6599
        $list .= '</ul>';
6600
6601
        $return .= Display::panelCollapse(
6602
            $this->name,
6603
            $list,
6604
            'scorm-list',
6605
            null,
6606
            'scorm-list-accordion',
6607
            'scorm-list-collapse'
6608
        );
6609
6610
        if ($update_audio == 'true') {
6611
            $return = $return_audio;
6612
        }
6613
6614
        return $return;
6615
    }
6616
6617
    /**
6618
     * @param array $elements
6619
     * @param array $default_data
6620
     * @param array $default_content
6621
     *
6622
     * @return string
6623
     */
6624
    public function print_recursive($elements, $default_data, $default_content)
6625
    {
6626
        $return = '';
6627
        foreach ($elements as $key => $item) {
6628
            if (isset($item['load_data']) || empty($item['data'])) {
6629
                $item['data'] = $default_data[$item['load_data']];
6630
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6631
            }
6632
            $sub_list = '';
6633
            if (isset($item['type']) && $item['type'] == 'dir') {
6634
                // empty value
6635
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6636
            }
6637
            if (empty($item['children'])) {
6638
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6639
                $active = null;
6640
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6641
                    $active = 'active';
6642
                }
6643
                $return .= Display::tag(
6644
                    'li',
6645
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6646
                    ['id' => $key, 'class' => 'record li_container']
6647
                );
6648
            } else {
6649
                // Sections
6650
                if (isset($item['children'])) {
6651
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6652
                }
6653
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6654
                $return .= Display::tag(
6655
                    'li',
6656
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6657
                    ['id' => $key, 'class' => 'record li_container']
6658
                );
6659
            }
6660
        }
6661
6662
        return $return;
6663
    }
6664
6665
    /**
6666
     * This function builds the action menu.
6667
     *
6668
     * @param bool $returnContent          Optional
6669
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6670
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6671
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6672
     *
6673
     * @return string
6674
     */
6675
    public function build_action_menu(
6676
        $returnContent = false,
6677
        $showRequirementButtons = true,
6678
        $isConfigPage = false,
6679
        $allowExpand = true
6680
    ) {
6681
        $actionsLeft = '';
6682
        $actionsRight = '';
6683
6684
        $actionsLeft .= Display::url(
6685
            Display::return_icon(
6686
                'preview_view.png',
6687
                get_lang('Preview'),
6688
                '',
6689
                ICON_SIZE_MEDIUM
6690
            ),
6691
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6692
                'action' => 'view',
6693
                'lp_id' => $this->lp_id,
6694
                'isStudentView' => 'true',
6695
            ])
6696
        );
6697
        $actionsLeft .= Display::url(
6698
            Display::return_icon(
6699
                'upload_audio.png',
6700
                get_lang('UpdateAllAudioFragments'),
6701
                '',
6702
                ICON_SIZE_MEDIUM
6703
            ),
6704
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6705
                'action' => 'admin_view',
6706
                'lp_id' => $this->lp_id,
6707
                'updateaudio' => 'true',
6708
            ])
6709
        );
6710
6711
        if (!$isConfigPage) {
6712
            $actionsLeft .= Display::url(
6713
                Display::return_icon(
6714
                    'settings.png',
6715
                    get_lang('CourseSettings'),
6716
                    '',
6717
                    ICON_SIZE_MEDIUM
6718
                ),
6719
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6720
                    'action' => 'edit',
6721
                    'lp_id' => $this->lp_id,
6722
                ])
6723
            );
6724
        } else {
6725
            $actionsLeft .= Display::url(
6726
                Display::return_icon(
6727
                    'edit.png',
6728
                    get_lang('Edit'),
6729
                    '',
6730
                    ICON_SIZE_MEDIUM
6731
                ),
6732
                'lp_controller.php?'.http_build_query([
6733
                    'action' => 'build',
6734
                    'lp_id' => $this->lp_id,
6735
                ]).'&'.api_get_cidreq()
6736
            );
6737
        }
6738
6739
        if ($allowExpand) {
6740
            $actionsLeft .= Display::url(
6741
                Display::return_icon(
6742
                    'expand.png',
6743
                    get_lang('Expand'),
6744
                    ['id' => 'expand'],
6745
                    ICON_SIZE_MEDIUM
6746
                ).
6747
                Display::return_icon(
6748
                    'contract.png',
6749
                    get_lang('Collapse'),
6750
                    ['id' => 'contract', 'class' => 'hide'],
6751
                    ICON_SIZE_MEDIUM
6752
                ),
6753
                '#',
6754
                ['role' => 'button', 'id' => 'hide_bar_template']
6755
            );
6756
        }
6757
6758
        if ($showRequirementButtons) {
6759
            $buttons = [
6760
                [
6761
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6762
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6763
                        'action' => 'set_previous_step_as_prerequisite',
6764
                        'lp_id' => $this->lp_id,
6765
                    ]),
6766
                ],
6767
                [
6768
                    'title' => get_lang('ClearAllPrerequisites'),
6769
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6770
                        'action' => 'clear_prerequisites',
6771
                        'lp_id' => $this->lp_id,
6772
                    ]),
6773
                ],
6774
            ];
6775
            $actionsRight = Display::groupButtonWithDropDown(
6776
                get_lang('PrerequisitesOptions'),
6777
                $buttons,
6778
                true
6779
            );
6780
        }
6781
6782
        $toolbar = Display::toolbarAction(
6783
            'actions-lp-controller',
6784
            [$actionsLeft, $actionsRight]
6785
        );
6786
6787
        if ($returnContent) {
6788
            return $toolbar;
6789
        }
6790
6791
        echo $toolbar;
6792
    }
6793
6794
    /**
6795
     * Creates the default learning path folder.
6796
     *
6797
     * @param array $course
6798
     * @param int   $creatorId
6799
     *
6800
     * @return bool
6801
     */
6802
    public static function generate_learning_path_folder($course, $creatorId = 0)
6803
    {
6804
        // Creating learning_path folder
6805
        $dir = '/learning_path';
6806
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6807
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6808
6809
        $folder = false;
6810
        if (!is_dir($filepath.'/'.$dir)) {
6811
            $folderData = create_unexisting_directory(
6812
                $course,
6813
                $creatorId,
6814
                0,
6815
                null,
6816
                0,
6817
                $filepath,
6818
                $dir,
6819
                get_lang('LearningPaths'),
6820
                0
6821
            );
6822
            if (!empty($folderData)) {
6823
                $folder = true;
6824
            }
6825
        } else {
6826
            $folder = true;
6827
        }
6828
6829
        return $folder;
6830
    }
6831
6832
    /**
6833
     * @param array  $course
6834
     * @param string $lp_name
6835
     * @param int    $creatorId
6836
     *
6837
     * @return array
6838
     */
6839
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6840
    {
6841
        $filepath = '';
6842
        $dir = '/learning_path/';
6843
6844
        if (empty($lp_name)) {
6845
            $lp_name = $this->name;
6846
        }
6847
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6848
6849
        $folder = self::generate_learning_path_folder($course, $creatorId);
6850
6851
        // Limits title size
6852
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6853
        $dir = $dir.$title;
6854
6855
        // Creating LP folder
6856
        $documentId = null;
6857
6858
        if ($folder) {
6859
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6860
            if (!is_dir($filepath.'/'.$dir)) {
6861
                $folderData = create_unexisting_directory(
6862
                    $course,
6863
                    $creatorId,
6864
                    0,
6865
                    0,
6866
                    0,
6867
                    $filepath,
6868
                    $dir,
6869
                    $lp_name
6870
                );
6871
                if (!empty($folderData)) {
6872
                    $folder = true;
6873
                }
6874
6875
                $documentId = $folderData['id'];
6876
            } else {
6877
                $folder = true;
6878
            }
6879
            $dir = $dir.'/';
6880
            if ($folder) {
6881
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6882
            }
6883
        }
6884
6885
        if (empty($documentId)) {
6886
            $dir = api_remove_trailing_slash($dir);
6887
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6888
        }
6889
6890
        $array = [
6891
            'dir' => $dir,
6892
            'filepath' => $filepath,
6893
            'folder' => $folder,
6894
            'id' => $documentId,
6895
        ];
6896
6897
        return $array;
6898
    }
6899
6900
    /**
6901
     * Create a new document //still needs some finetuning.
6902
     *
6903
     * @param array  $courseInfo
6904
     * @param string $content
6905
     * @param string $title
6906
     * @param string $extension
6907
     * @param int    $parentId
6908
     * @param int    $creatorId  creator id
6909
     *
6910
     * @return int
6911
     */
6912
    public function create_document(
6913
        $courseInfo,
6914
        $content = '',
6915
        $title = '',
6916
        $extension = 'html',
6917
        $parentId = 0,
6918
        $creatorId = 0
6919
    ) {
6920
        if (!empty($courseInfo)) {
6921
            $course_id = $courseInfo['real_id'];
6922
        } else {
6923
            $course_id = api_get_course_int_id();
6924
        }
6925
6926
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6927
        $sessionId = api_get_session_id();
6928
6929
        // Generates folder
6930
        $result = $this->generate_lp_folder($courseInfo);
6931
        $dir = $result['dir'];
6932
6933
        if (empty($parentId) || $parentId == '/') {
6934
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6935
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6936
6937
            if ($parentId === '/') {
6938
                $dir = '/';
6939
            }
6940
6941
            // Please, do not modify this dirname formatting.
6942
            if (strstr($dir, '..')) {
6943
                $dir = '/';
6944
            }
6945
6946
            if (!empty($dir[0]) && $dir[0] == '.') {
6947
                $dir = substr($dir, 1);
6948
            }
6949
            if (!empty($dir[0]) && $dir[0] != '/') {
6950
                $dir = '/'.$dir;
6951
            }
6952
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6953
                $dir .= '/';
6954
            }
6955
        } else {
6956
            $parentInfo = DocumentManager::get_document_data_by_id(
6957
                $parentId,
6958
                $courseInfo['code']
6959
            );
6960
            if (!empty($parentInfo)) {
6961
                $dir = $parentInfo['path'].'/';
6962
            }
6963
        }
6964
6965
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6966
        if (!is_dir($filepath)) {
6967
            $dir = '/';
6968
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6969
        }
6970
6971
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6972
        // is already escaped twice when it gets here.
6973
6974
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6975
        if (!empty($title)) {
6976
            $title = api_replace_dangerous_char(stripslashes($title));
6977
        } else {
6978
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6979
        }
6980
6981
        $title = disable_dangerous_file($title);
6982
        $filename = $title;
6983
        $content = !empty($content) ? $content : $_POST['content_lp'];
6984
        $tmp_filename = $filename;
6985
6986
        $i = 0;
6987
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6988
            $tmp_filename = $filename.'_'.++$i;
6989
        }
6990
6991
        $filename = $tmp_filename.'.'.$extension;
6992
        if ($extension == 'html') {
6993
            $content = stripslashes($content);
6994
            $content = str_replace(
6995
                api_get_path(WEB_COURSE_PATH),
6996
                api_get_path(REL_PATH).'courses/',
6997
                $content
6998
            );
6999
7000
            // Change the path of mp3 to absolute.
7001
7002
            // The first regexp deals with :// urls.
7003
            $content = preg_replace(
7004
                "|(flashvars=\"file=)([^:/]+)/|",
7005
                "$1".api_get_path(
7006
                    REL_COURSE_PATH
7007
                ).$courseInfo['path'].'/document/',
7008
                $content
7009
            );
7010
            // The second regexp deals with audio/ urls.
7011
            $content = preg_replace(
7012
                "|(flashvars=\"file=)([^/]+)/|",
7013
                "$1".api_get_path(
7014
                    REL_COURSE_PATH
7015
                ).$courseInfo['path'].'/document/$2/',
7016
                $content
7017
            );
7018
            // For flv player: To prevent edition problem with firefox,
7019
            // we have to use a strange tip (don't blame me please).
7020
            $content = str_replace(
7021
                '</body>',
7022
                '<style type="text/css">body{}</style></body>',
7023
                $content
7024
            );
7025
        }
7026
7027
        if (!file_exists($filepath.$filename)) {
7028
            if ($fp = @fopen($filepath.$filename, 'w')) {
7029
                fputs($fp, $content);
7030
                fclose($fp);
7031
7032
                $file_size = filesize($filepath.$filename);
7033
                $save_file_path = $dir.$filename;
7034
7035
                $document_id = add_document(
7036
                    $courseInfo,
7037
                    $save_file_path,
7038
                    'file',
7039
                    $file_size,
7040
                    $tmp_filename,
7041
                    '',
7042
                    0, //readonly
7043
                    true,
7044
                    null,
7045
                    $sessionId,
7046
                    $creatorId
7047
                );
7048
7049
                if ($document_id) {
7050
                    api_item_property_update(
7051
                        $courseInfo,
7052
                        TOOL_DOCUMENT,
7053
                        $document_id,
7054
                        'DocumentAdded',
7055
                        $creatorId,
7056
                        null,
7057
                        null,
7058
                        null,
7059
                        null,
7060
                        $sessionId
7061
                    );
7062
7063
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7064
                    $new_title = $originalTitle;
7065
7066
                    if ($new_comment || $new_title) {
7067
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7068
                        $ct = '';
7069
                        if ($new_comment) {
7070
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7071
                        }
7072
                        if ($new_title) {
7073
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7074
                        }
7075
7076
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7077
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7078
                        Database::query($sql);
7079
                    }
7080
                }
7081
7082
                return $document_id;
7083
            }
7084
        }
7085
    }
7086
7087
    /**
7088
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7089
     *
7090
     * @param array $_course array
7091
     */
7092
    public function edit_document($_course)
7093
    {
7094
        $course_id = api_get_course_int_id();
7095
        $urlAppend = api_get_configuration_value('url_append');
7096
        // Please, do not modify this dirname formatting.
7097
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7098
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7099
7100
        if (strstr($dir, '..')) {
7101
            $dir = '/';
7102
        }
7103
7104
        if (isset($dir[0]) && $dir[0] == '.') {
7105
            $dir = substr($dir, 1);
7106
        }
7107
7108
        if (isset($dir[0]) && $dir[0] != '/') {
7109
            $dir = '/'.$dir;
7110
        }
7111
7112
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7113
            $dir .= '/';
7114
        }
7115
7116
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7117
7118
        if (!is_dir($filepath)) {
7119
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7120
        }
7121
7122
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7123
7124
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7125
            $document_id = intval($_POST['path']);
7126
            $sql = "SELECT path FROM ".$table_doc."
7127
                    WHERE c_id = $course_id AND id = ".$document_id;
7128
            $res = Database::query($sql);
7129
            $row = Database::fetch_array($res);
7130
            $content = stripslashes($_POST['content_lp']);
7131
            $file = $filepath.$row['path'];
7132
7133
            if ($fp = @fopen($file, 'w')) {
7134
                $content = str_replace(
7135
                    api_get_path(WEB_COURSE_PATH),
7136
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7137
                    $content
7138
                );
7139
                // Change the path of mp3 to absolute.
7140
                // The first regexp deals with :// urls.
7141
                $content = preg_replace(
7142
                    "|(flashvars=\"file=)([^:/]+)/|",
7143
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7144
                    $content
7145
                );
7146
                // The second regexp deals with audio/ urls.
7147
                $content = preg_replace(
7148
                    "|(flashvars=\"file=)([^:/]+)/|",
7149
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7150
                    $content
7151
                );
7152
                fputs($fp, $content);
7153
                fclose($fp);
7154
7155
                $sql = "UPDATE ".$table_doc." SET
7156
                            title='".Database::escape_string($_POST['title'])."'
7157
                        WHERE c_id = ".$course_id." AND id = ".$document_id;
7158
                Database::query($sql);
7159
            }
7160
        }
7161
    }
7162
7163
    /**
7164
     * Displays the selected item, with a panel for manipulating the item.
7165
     *
7166
     * @param int    $item_id
7167
     * @param string $msg
7168
     * @param bool   $show_actions
7169
     *
7170
     * @return string
7171
     */
7172
    public function display_item($item_id, $msg = null, $show_actions = true)
7173
    {
7174
        $course_id = api_get_course_int_id();
7175
        $return = '';
7176
        if (is_numeric($item_id)) {
7177
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7178
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7179
                    WHERE lp.iid = ".intval($item_id);
7180
            $result = Database::query($sql);
7181
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7182
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7183
7184
                // Prevents wrong parent selection for document, see Bug#1251.
7185
                if ($row['item_type'] != 'dir') {
7186
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7187
                }
7188
7189
                if ($show_actions) {
7190
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7191
                }
7192
                $return .= '<div style="padding:10px;">';
7193
7194
                if ($msg != '') {
7195
                    $return .= $msg;
7196
                }
7197
7198
                $return .= '<h3>'.$row['title'].'</h3>';
7199
7200
                switch ($row['item_type']) {
7201
                    case TOOL_THREAD:
7202
                        $link = $this->rl_get_resource_link_for_learnpath(
7203
                            $course_id,
7204
                            $row['lp_id'],
7205
                            $item_id,
7206
                            0
7207
                        );
7208
                        $return .= Display::url(
7209
                            get_lang('GoToThread'),
7210
                            $link,
7211
                            ['class' => 'btn btn-primary']
7212
                        );
7213
                        break;
7214
                    case TOOL_FORUM:
7215
                        $return .= Display::url(
7216
                            get_lang('GoToForum'),
7217
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7218
                            ['class' => 'btn btn-primary']
7219
                        );
7220
                        break;
7221
                    case TOOL_QUIZ:
7222
                        if (!empty($row['path'])) {
7223
                            $exercise = new Exercise();
7224
                            $exercise->read($row['path']);
7225
                            $return .= $exercise->description.'<br />';
7226
                            $return .= Display::url(
7227
                                get_lang('GoToExercise'),
7228
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7229
                                ['class' => 'btn btn-primary']
7230
                            );
7231
                        }
7232
                        break;
7233
                    case TOOL_LP_FINAL_ITEM:
7234
                        $return .= $this->getSavedFinalItem();
7235
                        break;
7236
                    case TOOL_DOCUMENT:
7237
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7238
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7239
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7240
                        $result = Database::query($sql_doc);
7241
                        $path_file = Database::result($result, 0, 0);
7242
                        $path_parts = pathinfo($path_file);
7243
                        // TODO: Correct the following naive comparisons.
7244
                        if (in_array($path_parts['extension'], [
7245
                            'html',
7246
                            'txt',
7247
                            'png',
7248
                            'jpg',
7249
                            'JPG',
7250
                            'jpeg',
7251
                            'JPEG',
7252
                            'gif',
7253
                            'swf',
7254
                            'pdf',
7255
                            'htm',
7256
                        ])) {
7257
                            $return .= $this->display_document($row['path'], true, true);
7258
                        }
7259
                        break;
7260
                    case TOOL_HOTPOTATOES:
7261
                        $return .= $this->display_document($row['path'], false, true);
7262
                        break;
7263
                }
7264
                $return .= '</div>';
7265
            }
7266
        }
7267
7268
        return $return;
7269
    }
7270
7271
    /**
7272
     * Shows the needed forms for editing a specific item.
7273
     *
7274
     * @param int $item_id
7275
     *
7276
     * @throws Exception
7277
     * @throws HTML_QuickForm_Error
7278
     *
7279
     * @return string
7280
     */
7281
    public function display_edit_item($item_id)
7282
    {
7283
        $course_id = api_get_course_int_id();
7284
        $return = '';
7285
        $item_id = (int) $item_id;
7286
7287
        if (empty($item_id)) {
7288
            return '';
7289
        }
7290
7291
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7292
        $sql = "SELECT * FROM $tbl_lp_item
7293
                WHERE iid = ".$item_id;
7294
        $res = Database::query($sql);
7295
        $row = Database::fetch_array($res);
7296
        switch ($row['item_type']) {
7297
            case 'dir':
7298
            case 'asset':
7299
            case 'sco':
7300
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7301
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7302
                    $return .= $this->display_item_form(
7303
                        $row['item_type'],
7304
                        get_lang('EditCurrentChapter').' :',
7305
                        'edit',
7306
                        $item_id,
7307
                        $row
7308
                    );
7309
                } else {
7310
                    $return .= $this->display_item_small_form(
7311
                        $row['item_type'],
7312
                        get_lang('EditCurrentChapter').' :',
7313
                        $row
7314
                    );
7315
                }
7316
                break;
7317
            case TOOL_DOCUMENT:
7318
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7319
                $sql = "SELECT lp.*, doc.path as dir
7320
                        FROM $tbl_lp_item as lp
7321
                        LEFT JOIN $tbl_doc as doc
7322
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7323
                        WHERE
7324
                            doc.c_id = $course_id AND
7325
                            lp.iid = ".$item_id;
7326
                $res_step = Database::query($sql);
7327
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7328
                $return .= $this->display_manipulate(
7329
                    $item_id,
7330
                    $row['item_type']
7331
                );
7332
                $return .= $this->display_document_form(
7333
                    'edit',
7334
                    $item_id,
7335
                    $row_step
7336
                );
7337
                break;
7338
            case TOOL_LINK:
7339
                $link_id = (string) $row['path'];
7340
                if (ctype_digit($link_id)) {
7341
                    $tbl_link = Database::get_course_table(TABLE_LINK);
7342
                    $sql_select = 'SELECT url FROM '.$tbl_link.'
7343
                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7344
                    $res_link = Database::query($sql_select);
7345
                    $row_link = Database::fetch_array($res_link);
7346
                    if (is_array($row_link)) {
7347
                        $row['url'] = $row_link['url'];
7348
                    }
7349
                }
7350
                $return .= $this->display_manipulate(
7351
                    $item_id,
7352
                    $row['item_type']
7353
                );
7354
                $return .= $this->display_link_form('edit', $item_id, $row);
7355
                break;
7356
            case TOOL_LP_FINAL_ITEM:
7357
                Session::write('finalItem', true);
7358
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7359
                $sql = "SELECT lp.*, doc.path as dir
7360
                        FROM $tbl_lp_item as lp
7361
                        LEFT JOIN $tbl_doc as doc
7362
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7363
                        WHERE
7364
                            doc.c_id = $course_id AND
7365
                            lp.iid = ".$item_id;
7366
                $res_step = Database::query($sql);
7367
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7368
                $return .= $this->display_manipulate(
7369
                    $item_id,
7370
                    $row['item_type']
7371
                );
7372
                $return .= $this->display_document_form(
7373
                    'edit',
7374
                    $item_id,
7375
                    $row_step
7376
                );
7377
                break;
7378
            case TOOL_QUIZ:
7379
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7380
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7381
                break;
7382
            case TOOL_HOTPOTATOES:
7383
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7384
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7385
                break;
7386
            case TOOL_STUDENTPUBLICATION:
7387
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7388
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7389
                break;
7390
            case TOOL_FORUM:
7391
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7392
                $return .= $this->display_forum_form('edit', $item_id, $row);
7393
                break;
7394
            case TOOL_THREAD:
7395
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7396
                $return .= $this->display_thread_form('edit', $item_id, $row);
7397
                break;
7398
        }
7399
7400
        return $return;
7401
    }
7402
7403
    /**
7404
     * Function that displays a list with al the resources that
7405
     * could be added to the learning path.
7406
     *
7407
     * @throws Exception
7408
     * @throws HTML_QuickForm_Error
7409
     *
7410
     * @return bool
7411
     */
7412
    public function display_resources()
7413
    {
7414
        $course_code = api_get_course_id();
7415
7416
        // Get all the docs.
7417
        $documents = $this->get_documents(true);
7418
7419
        // Get all the exercises.
7420
        $exercises = $this->get_exercises();
7421
7422
        // Get all the links.
7423
        $links = $this->get_links();
7424
7425
        // Get all the student publications.
7426
        $works = $this->get_student_publications();
7427
7428
        // Get all the forums.
7429
        $forums = $this->get_forums(null, $course_code);
7430
7431
        // Get the final item form (see BT#11048) .
7432
        $finish = $this->getFinalItemForm();
7433
7434
        $headers = [
7435
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7436
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7437
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7438
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7439
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7440
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7441
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7442
        ];
7443
7444
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7445
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7446
        echo Display::tabs(
7447
            $headers,
7448
            [
7449
                $documents,
7450
                $exercises,
7451
                $links,
7452
                $works,
7453
                $forums,
7454
                $dir,
7455
                $finish,
7456
            ],
7457
            'resource_tab'
7458
        );
7459
7460
        return true;
7461
    }
7462
7463
    /**
7464
     * Returns the extension of a document.
7465
     *
7466
     * @param string $filename
7467
     *
7468
     * @return string Extension (part after the last dot)
7469
     */
7470
    public function get_extension($filename)
7471
    {
7472
        $explode = explode('.', $filename);
7473
7474
        return $explode[count($explode) - 1];
7475
    }
7476
7477
    /**
7478
     * Displays a document by id.
7479
     *
7480
     * @param int  $id
7481
     * @param bool $show_title
7482
     * @param bool $iframe
7483
     * @param bool $edit_link
7484
     *
7485
     * @return string
7486
     */
7487
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7488
    {
7489
        $_course = api_get_course_info();
7490
        $course_id = api_get_course_int_id();
7491
        $id = (int) $id;
7492
        $return = '';
7493
        $table = Database::get_course_table(TABLE_DOCUMENT);
7494
        $sql_doc = "SELECT * FROM $table
7495
                    WHERE c_id = $course_id AND iid = $id";
7496
        $res_doc = Database::query($sql_doc);
7497
        $row_doc = Database::fetch_array($res_doc);
7498
7499
        // TODO: Add a path filter.
7500
        if ($iframe) {
7501
            $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>';
7502
        } else {
7503
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7504
        }
7505
7506
        return $return;
7507
    }
7508
7509
    /**
7510
     * Return HTML form to add/edit a quiz.
7511
     *
7512
     * @param string $action     Action (add/edit)
7513
     * @param int    $id         Item ID if already exists
7514
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7515
     *
7516
     * @throws Exception
7517
     *
7518
     * @return string HTML form
7519
     */
7520
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7521
    {
7522
        $course_id = api_get_course_int_id();
7523
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7524
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7525
7526
        if ($id != 0 && is_array($extra_info)) {
7527
            $item_title = $extra_info['title'];
7528
            $item_description = $extra_info['description'];
7529
        } elseif (is_numeric($extra_info)) {
7530
            $sql = "SELECT title, description
7531
                    FROM $tbl_quiz
7532
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7533
7534
            $result = Database::query($sql);
7535
            $row = Database::fetch_array($result);
7536
            $item_title = $row['title'];
7537
            $item_description = $row['description'];
7538
        } else {
7539
            $item_title = '';
7540
            $item_description = '';
7541
        }
7542
        $item_title = Security::remove_XSS($item_title);
7543
        $item_description = Security::remove_XSS($item_description);
7544
7545
        if ($id != 0 && is_array($extra_info)) {
7546
            $parent = $extra_info['parent_item_id'];
7547
        } else {
7548
            $parent = 0;
7549
        }
7550
7551
        $sql = "SELECT * FROM $tbl_lp_item 
7552
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7553
7554
        $result = Database::query($sql);
7555
        $arrLP = [];
7556
        while ($row = Database::fetch_array($result)) {
7557
            $arrLP[] = [
7558
                'id' => $row['iid'],
7559
                'item_type' => $row['item_type'],
7560
                'title' => $row['title'],
7561
                'path' => $row['path'],
7562
                'description' => $row['description'],
7563
                'parent_item_id' => $row['parent_item_id'],
7564
                'previous_item_id' => $row['previous_item_id'],
7565
                'next_item_id' => $row['next_item_id'],
7566
                'display_order' => $row['display_order'],
7567
                'max_score' => $row['max_score'],
7568
                'min_score' => $row['min_score'],
7569
                'mastery_score' => $row['mastery_score'],
7570
                'prerequisite' => $row['prerequisite'],
7571
                'max_time_allowed' => $row['max_time_allowed'],
7572
            ];
7573
        }
7574
7575
        $this->tree_array($arrLP);
7576
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7577
        unset($this->arrMenu);
7578
7579
        $form = new FormValidator(
7580
            'quiz_form',
7581
            'POST',
7582
            $this->getCurrentBuildingModeURL()
7583
        );
7584
        $defaults = [];
7585
7586
        if ($action == 'add') {
7587
            $legend = get_lang('CreateTheExercise');
7588
        } elseif ($action == 'move') {
7589
            $legend = get_lang('MoveTheCurrentExercise');
7590
        } else {
7591
            $legend = get_lang('EditCurrentExecice');
7592
        }
7593
7594
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7595
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7596
        }
7597
7598
        $form->addHeader($legend);
7599
7600
        if ($action != 'move') {
7601
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7602
            $defaults['title'] = $item_title;
7603
        }
7604
7605
        // Select for Parent item, root or chapter
7606
        $selectParent = $form->addSelect(
7607
            'parent',
7608
            get_lang('Parent'),
7609
            [],
7610
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7611
        );
7612
        $selectParent->addOption($this->name, 0);
7613
7614
        $arrHide = [
7615
            $id,
7616
        ];
7617
        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...
7618
            if ($action != 'add') {
7619
                if (
7620
                    ($arrLP[$i]['item_type'] == 'dir') &&
7621
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7622
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7623
                ) {
7624
                    $selectParent->addOption(
7625
                        $arrLP[$i]['title'],
7626
                        $arrLP[$i]['id'],
7627
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7628
                    );
7629
7630
                    if ($parent == $arrLP[$i]['id']) {
7631
                        $selectParent->setSelected($arrLP[$i]['id']);
7632
                    }
7633
                } else {
7634
                    $arrHide[] = $arrLP[$i]['id'];
7635
                }
7636
            } else {
7637
                if ($arrLP[$i]['item_type'] == 'dir') {
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
                }
7648
            }
7649
        }
7650
        if (is_array($arrLP)) {
7651
            reset($arrLP);
7652
        }
7653
7654
        $selectPrevious = $form->addSelect(
7655
            'previous',
7656
            get_lang('Position'),
7657
            [],
7658
            ['id' => 'previous']
7659
        );
7660
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7661
7662
        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...
7663
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7664
                $arrLP[$i]['id'] != $id
7665
            ) {
7666
                $selectPrevious->addOption(
7667
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7668
                    $arrLP[$i]['id']
7669
                );
7670
7671
                if (is_array($extra_info)) {
7672
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7673
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7674
                    }
7675
                } elseif ($action == 'add') {
7676
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7677
                }
7678
            }
7679
        }
7680
7681
        if ($action != 'move') {
7682
            $id_prerequisite = 0;
7683
            if (is_array($arrLP)) {
7684
                foreach ($arrLP as $key => $value) {
7685
                    if ($value['id'] == $id) {
7686
                        $id_prerequisite = $value['prerequisite'];
7687
                        break;
7688
                    }
7689
                }
7690
            }
7691
            $arrHide = [];
7692
            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...
7693
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7694
                    if (is_array($extra_info)) {
7695
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7696
                            $s_selected_position = $arrLP[$i]['id'];
7697
                        }
7698
                    } elseif ($action == 'add') {
7699
                        $s_selected_position = 0;
7700
                    }
7701
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7702
                }
7703
            }
7704
        }
7705
7706
        if ($action == 'add') {
7707
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7708
        } else {
7709
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7710
        }
7711
7712
        if ($action == 'move') {
7713
            $form->addHidden('title', $item_title);
7714
            $form->addHidden('description', $item_description);
7715
        }
7716
7717
        if (is_numeric($extra_info)) {
7718
            $form->addHidden('path', $extra_info);
7719
        } elseif (is_array($extra_info)) {
7720
            $form->addHidden('path', $extra_info['path']);
7721
        }
7722
7723
        $form->addHidden('type', TOOL_QUIZ);
7724
        $form->addHidden('post_time', time());
7725
        $form->setDefaults($defaults);
7726
7727
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7728
    }
7729
7730
    /**
7731
     * Addition of Hotpotatoes tests.
7732
     *
7733
     * @param string $action
7734
     * @param int    $id         Internal ID of the item
7735
     * @param string $extra_info
7736
     *
7737
     * @return string HTML structure to display the hotpotatoes addition formular
7738
     */
7739
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7740
    {
7741
        $course_id = api_get_course_int_id();
7742
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7743
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7744
7745
        if ($id != 0 && is_array($extra_info)) {
7746
            $item_title = stripslashes($extra_info['title']);
7747
            $item_description = stripslashes($extra_info['description']);
7748
        } elseif (is_numeric($extra_info)) {
7749
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7750
7751
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7752
                    WHERE
7753
                        c_id = ".$course_id." AND
7754
                        path LIKE '".$uploadPath."/%/%htm%' AND
7755
                        iid = ".(int) $extra_info."
7756
                    ORDER BY iid ASC";
7757
7758
            $res_hot = Database::query($sql);
7759
            $row = Database::fetch_array($res_hot);
7760
7761
            $item_title = $row['title'];
7762
            $item_description = $row['description'];
7763
7764
            if (!empty($row['comment'])) {
7765
                $item_title = $row['comment'];
7766
            }
7767
        } else {
7768
            $item_title = '';
7769
            $item_description = '';
7770
        }
7771
7772
        if ($id != 0 && is_array($extra_info)) {
7773
            $parent = $extra_info['parent_item_id'];
7774
        } else {
7775
            $parent = 0;
7776
        }
7777
7778
        $sql = "SELECT * FROM $tbl_lp_item
7779
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7780
        $result = Database::query($sql);
7781
        $arrLP = [];
7782
        while ($row = Database::fetch_array($result)) {
7783
            $arrLP[] = [
7784
                'id' => $row['id'],
7785
                'item_type' => $row['item_type'],
7786
                'title' => $row['title'],
7787
                'path' => $row['path'],
7788
                'description' => $row['description'],
7789
                'parent_item_id' => $row['parent_item_id'],
7790
                'previous_item_id' => $row['previous_item_id'],
7791
                'next_item_id' => $row['next_item_id'],
7792
                'display_order' => $row['display_order'],
7793
                'max_score' => $row['max_score'],
7794
                'min_score' => $row['min_score'],
7795
                'mastery_score' => $row['mastery_score'],
7796
                'prerequisite' => $row['prerequisite'],
7797
                'max_time_allowed' => $row['max_time_allowed'],
7798
            ];
7799
        }
7800
7801
        $legend = '<legend>';
7802
        if ($action == 'add') {
7803
            $legend .= get_lang('CreateTheExercise');
7804
        } elseif ($action == 'move') {
7805
            $legend .= get_lang('MoveTheCurrentExercise');
7806
        } else {
7807
            $legend .= get_lang('EditCurrentExecice');
7808
        }
7809
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7810
            $legend .= Display:: return_message(
7811
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7812
            );
7813
        }
7814
        $legend .= '</legend>';
7815
7816
        $return = '<form method="POST">';
7817
        $return .= $legend;
7818
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7819
        $return .= '<tr>';
7820
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7821
        $return .= '<td class="input">';
7822
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7823
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7824
        $arrHide = [
7825
            $id,
7826
        ];
7827
7828
        if (count($arrLP) > 0) {
7829
            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...
7830
                if ($action != 'add') {
7831
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7832
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7833
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7834
                    ) {
7835
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7836
                    } else {
7837
                        $arrHide[] = $arrLP[$i]['id'];
7838
                    }
7839
                } else {
7840
                    if ($arrLP[$i]['item_type'] == 'dir') {
7841
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7842
                    }
7843
                }
7844
            }
7845
            reset($arrLP);
7846
        }
7847
7848
        $return .= '</select>';
7849
        $return .= '</td>';
7850
        $return .= '</tr>';
7851
        $return .= '<tr>';
7852
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7853
        $return .= '<td class="input">';
7854
        $return .= '<select id="previous" name="previous" size="1">';
7855
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7856
7857
        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...
7858
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7859
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7860
                    $selected = 'selected="selected" ';
7861
                } elseif ($action == 'add') {
7862
                    $selected = 'selected="selected" ';
7863
                } else {
7864
                    $selected = '';
7865
                }
7866
7867
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7868
            }
7869
        }
7870
7871
        $return .= '</select>';
7872
        $return .= '</td>';
7873
        $return .= '</tr>';
7874
7875
        if ($action != 'move') {
7876
            $return .= '<tr>';
7877
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7878
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7879
            $return .= '</tr>';
7880
            $id_prerequisite = 0;
7881
            if (is_array($arrLP) && count($arrLP) > 0) {
7882
                foreach ($arrLP as $key => $value) {
7883
                    if ($value['id'] == $id) {
7884
                        $id_prerequisite = $value['prerequisite'];
7885
                        break;
7886
                    }
7887
                }
7888
7889
                $arrHide = [];
7890
                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...
7891
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7892
                        if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7893
                            $s_selected_position = $arrLP[$i]['id'];
7894
                        } elseif ($action == 'add') {
7895
                            $s_selected_position = 0;
7896
                        }
7897
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7898
                    }
7899
                }
7900
            }
7901
        }
7902
7903
        $return .= '<tr>';
7904
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.get_lang('SaveHotpotatoes').'</button></td>';
7905
        $return .= '</tr>';
7906
        $return .= '</table>';
7907
7908
        if ($action == 'move') {
7909
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7910
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7911
        }
7912
7913
        if (is_numeric($extra_info)) {
7914
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7915
        } elseif (is_array($extra_info)) {
7916
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7917
        }
7918
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7919
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7920
        $return .= '</form>';
7921
7922
        return $return;
7923
    }
7924
7925
    /**
7926
     * Return the form to display the forum edit/add option.
7927
     *
7928
     * @param string $action
7929
     * @param int    $id         ID of the lp_item if already exists
7930
     * @param string $extra_info
7931
     *
7932
     * @throws Exception
7933
     *
7934
     * @return string HTML form
7935
     */
7936
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7937
    {
7938
        $course_id = api_get_course_int_id();
7939
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7940
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7941
7942
        if ($id != 0 && is_array($extra_info)) {
7943
            $item_title = stripslashes($extra_info['title']);
7944
        } elseif (is_numeric($extra_info)) {
7945
            $sql = "SELECT forum_title as title, forum_comment as comment
7946
                    FROM ".$tbl_forum."
7947
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
7948
7949
            $result = Database::query($sql);
7950
            $row = Database::fetch_array($result);
7951
7952
            $item_title = $row['title'];
7953
            $item_description = $row['comment'];
7954
        } else {
7955
            $item_title = '';
7956
            $item_description = '';
7957
        }
7958
7959
        if ($id != 0 && is_array($extra_info)) {
7960
            $parent = $extra_info['parent_item_id'];
7961
        } else {
7962
            $parent = 0;
7963
        }
7964
7965
        $sql = "SELECT * FROM $tbl_lp_item
7966
                WHERE
7967
                    c_id = $course_id AND
7968
                    lp_id = ".$this->lp_id;
7969
        $result = Database::query($sql);
7970
        $arrLP = [];
7971
        while ($row = Database::fetch_array($result)) {
7972
            $arrLP[] = [
7973
                'id' => $row['iid'],
7974
                'item_type' => $row['item_type'],
7975
                'title' => $row['title'],
7976
                'path' => $row['path'],
7977
                'description' => $row['description'],
7978
                'parent_item_id' => $row['parent_item_id'],
7979
                'previous_item_id' => $row['previous_item_id'],
7980
                'next_item_id' => $row['next_item_id'],
7981
                'display_order' => $row['display_order'],
7982
                'max_score' => $row['max_score'],
7983
                'min_score' => $row['min_score'],
7984
                'mastery_score' => $row['mastery_score'],
7985
                'prerequisite' => $row['prerequisite'],
7986
            ];
7987
        }
7988
7989
        $this->tree_array($arrLP);
7990
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7991
        unset($this->arrMenu);
7992
7993
        if ($action == 'add') {
7994
            $legend = get_lang('CreateTheForum');
7995
        } elseif ($action == 'move') {
7996
            $legend = get_lang('MoveTheCurrentForum');
7997
        } else {
7998
            $legend = get_lang('EditCurrentForum');
7999
        }
8000
8001
        $form = new FormValidator(
8002
            'forum_form',
8003
            'POST',
8004
            $this->getCurrentBuildingModeURL()
8005
        );
8006
        $defaults = [];
8007
8008
        $form->addHeader($legend);
8009
8010
        if ($action != 'move') {
8011
            $form->addText(
8012
                'title',
8013
                get_lang('Title'),
8014
                true,
8015
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8016
            );
8017
            $defaults['title'] = $item_title;
8018
        }
8019
8020
        $selectParent = $form->addSelect(
8021
            'parent',
8022
            get_lang('Parent'),
8023
            [],
8024
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8025
        );
8026
        $selectParent->addOption($this->name, 0);
8027
        $arrHide = [
8028
            $id,
8029
        ];
8030
        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...
8031
            if ($action != 'add') {
8032
                if ($arrLP[$i]['item_type'] == 'dir' &&
8033
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8034
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8035
                ) {
8036
                    $selectParent->addOption(
8037
                        $arrLP[$i]['title'],
8038
                        $arrLP[$i]['id'],
8039
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8040
                    );
8041
8042
                    if ($parent == $arrLP[$i]['id']) {
8043
                        $selectParent->setSelected($arrLP[$i]['id']);
8044
                    }
8045
                } else {
8046
                    $arrHide[] = $arrLP[$i]['id'];
8047
                }
8048
            } else {
8049
                if ($arrLP[$i]['item_type'] == 'dir') {
8050
                    $selectParent->addOption(
8051
                        $arrLP[$i]['title'],
8052
                        $arrLP[$i]['id'],
8053
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8054
                    );
8055
8056
                    if ($parent == $arrLP[$i]['id']) {
8057
                        $selectParent->setSelected($arrLP[$i]['id']);
8058
                    }
8059
                }
8060
            }
8061
        }
8062
8063
        if (is_array($arrLP)) {
8064
            reset($arrLP);
8065
        }
8066
8067
        $selectPrevious = $form->addSelect(
8068
            'previous',
8069
            get_lang('Position'),
8070
            [],
8071
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8072
        );
8073
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8074
8075
        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...
8076
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8077
                $arrLP[$i]['id'] != $id
8078
            ) {
8079
                $selectPrevious->addOption(
8080
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8081
                    $arrLP[$i]['id']
8082
                );
8083
8084
                if (isset($extra_info['previous_item_id']) &&
8085
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8086
                ) {
8087
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8088
                } elseif ($action == 'add') {
8089
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8090
                }
8091
            }
8092
        }
8093
8094
        if ($action != 'move') {
8095
            $id_prerequisite = 0;
8096
            if (is_array($arrLP)) {
8097
                foreach ($arrLP as $key => $value) {
8098
                    if ($value['id'] == $id) {
8099
                        $id_prerequisite = $value['prerequisite'];
8100
                        break;
8101
                    }
8102
                }
8103
            }
8104
8105
            $arrHide = [];
8106
            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...
8107
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8108
                    if (isset($extra_info['previous_item_id']) &&
8109
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8110
                    ) {
8111
                        $s_selected_position = $arrLP[$i]['id'];
8112
                    } elseif ($action == 'add') {
8113
                        $s_selected_position = 0;
8114
                    }
8115
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8116
                }
8117
            }
8118
        }
8119
8120
        if ($action == 'add') {
8121
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8122
        } else {
8123
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8124
        }
8125
8126
        if ($action == 'move') {
8127
            $form->addHidden('title', $item_title);
8128
            $form->addHidden('description', $item_description);
8129
        }
8130
8131
        if (is_numeric($extra_info)) {
8132
            $form->addHidden('path', $extra_info);
8133
        } elseif (is_array($extra_info)) {
8134
            $form->addHidden('path', $extra_info['path']);
8135
        }
8136
        $form->addHidden('type', TOOL_FORUM);
8137
        $form->addHidden('post_time', time());
8138
        $form->setDefaults($defaults);
8139
8140
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8141
    }
8142
8143
    /**
8144
     * Return HTML form to add/edit forum threads.
8145
     *
8146
     * @param string $action
8147
     * @param int    $id         Item ID if already exists in learning path
8148
     * @param string $extra_info
8149
     *
8150
     * @throws Exception
8151
     *
8152
     * @return string HTML form
8153
     */
8154
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8155
    {
8156
        $course_id = api_get_course_int_id();
8157
        if (empty($course_id)) {
8158
            return null;
8159
        }
8160
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8161
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8162
8163
        if ($id != 0 && is_array($extra_info)) {
8164
            $item_title = stripslashes($extra_info['title']);
8165
        } elseif (is_numeric($extra_info)) {
8166
            $sql = "SELECT thread_title as title FROM $tbl_forum
8167
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8168
8169
            $result = Database::query($sql);
8170
            $row = Database::fetch_array($result);
8171
8172
            $item_title = $row['title'];
8173
            $item_description = '';
8174
        } else {
8175
            $item_title = '';
8176
            $item_description = '';
8177
        }
8178
8179
        if ($id != 0 && is_array($extra_info)) {
8180
            $parent = $extra_info['parent_item_id'];
8181
        } else {
8182
            $parent = 0;
8183
        }
8184
8185
        $sql = "SELECT * FROM $tbl_lp_item
8186
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8187
        $result = Database::query($sql);
8188
8189
        $arrLP = [];
8190
        while ($row = Database::fetch_array($result)) {
8191
            $arrLP[] = [
8192
                'id' => $row['iid'],
8193
                'item_type' => $row['item_type'],
8194
                'title' => $row['title'],
8195
                'path' => $row['path'],
8196
                'description' => $row['description'],
8197
                'parent_item_id' => $row['parent_item_id'],
8198
                'previous_item_id' => $row['previous_item_id'],
8199
                'next_item_id' => $row['next_item_id'],
8200
                'display_order' => $row['display_order'],
8201
                'max_score' => $row['max_score'],
8202
                'min_score' => $row['min_score'],
8203
                'mastery_score' => $row['mastery_score'],
8204
                'prerequisite' => $row['prerequisite'],
8205
            ];
8206
        }
8207
8208
        $this->tree_array($arrLP);
8209
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8210
        unset($this->arrMenu);
8211
8212
        $form = new FormValidator(
8213
            'thread_form',
8214
            'POST',
8215
            $this->getCurrentBuildingModeURL()
8216
        );
8217
        $defaults = [];
8218
8219
        if ($action == 'add') {
8220
            $legend = get_lang('CreateTheForum');
8221
        } elseif ($action == 'move') {
8222
            $legend = get_lang('MoveTheCurrentForum');
8223
        } else {
8224
            $legend = get_lang('EditCurrentForum');
8225
        }
8226
8227
        $form->addHeader($legend);
8228
        $selectParent = $form->addSelect(
8229
            'parent',
8230
            get_lang('Parent'),
8231
            [],
8232
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8233
        );
8234
        $selectParent->addOption($this->name, 0);
8235
8236
        $arrHide = [
8237
            $id,
8238
        ];
8239
8240
        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...
8241
            if ($action != 'add') {
8242
                if (
8243
                    ($arrLP[$i]['item_type'] == 'dir') &&
8244
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8245
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8246
                ) {
8247
                    $selectParent->addOption(
8248
                        $arrLP[$i]['title'],
8249
                        $arrLP[$i]['id'],
8250
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8251
                    );
8252
8253
                    if ($parent == $arrLP[$i]['id']) {
8254
                        $selectParent->setSelected($arrLP[$i]['id']);
8255
                    }
8256
                } else {
8257
                    $arrHide[] = $arrLP[$i]['id'];
8258
                }
8259
            } else {
8260
                if ($arrLP[$i]['item_type'] == 'dir') {
8261
                    $selectParent->addOption(
8262
                        $arrLP[$i]['title'],
8263
                        $arrLP[$i]['id'],
8264
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8265
                    );
8266
8267
                    if ($parent == $arrLP[$i]['id']) {
8268
                        $selectParent->setSelected($arrLP[$i]['id']);
8269
                    }
8270
                }
8271
            }
8272
        }
8273
8274
        if ($arrLP != null) {
8275
            reset($arrLP);
8276
        }
8277
8278
        $selectPrevious = $form->addSelect(
8279
            'previous',
8280
            get_lang('Position'),
8281
            [],
8282
            ['id' => 'previous']
8283
        );
8284
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8285
8286
        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...
8287
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8288
                $selectPrevious->addOption(
8289
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8290
                    $arrLP[$i]['id']
8291
                );
8292
8293
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8294
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8295
                } elseif ($action == 'add') {
8296
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8297
                }
8298
            }
8299
        }
8300
8301
        if ($action != 'move') {
8302
            $form->addText(
8303
                'title',
8304
                get_lang('Title'),
8305
                true,
8306
                ['id' => 'idTitle']
8307
            );
8308
            $defaults['title'] = $item_title;
8309
8310
            $id_prerequisite = 0;
8311
            if ($arrLP != null) {
8312
                foreach ($arrLP as $key => $value) {
8313
                    if ($value['id'] == $id) {
8314
                        $id_prerequisite = $value['prerequisite'];
8315
                        break;
8316
                    }
8317
                }
8318
            }
8319
8320
            $arrHide = [];
8321
            $s_selected_position = 0;
8322
            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...
8323
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8324
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8325
                        $s_selected_position = $arrLP[$i]['id'];
8326
                    } elseif ($action == 'add') {
8327
                        $s_selected_position = 0;
8328
                    }
8329
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8330
                }
8331
            }
8332
8333
            $selectPrerequisites = $form->addSelect(
8334
                'prerequisites',
8335
                get_lang('LearnpathPrerequisites'),
8336
                [],
8337
                ['id' => 'prerequisites']
8338
            );
8339
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8340
8341
            foreach ($arrHide as $key => $value) {
8342
                $selectPrerequisites->addOption($value['value'], $key);
8343
8344
                if ($key == $s_selected_position && $action == 'add') {
8345
                    $selectPrerequisites->setSelected($key);
8346
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8347
                    $selectPrerequisites->setSelected($key);
8348
                }
8349
            }
8350
        }
8351
8352
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8353
8354
        if ($action == 'move') {
8355
            $form->addHidden('title', $item_title);
8356
            $form->addHidden('description', $item_description);
8357
        }
8358
8359
        if (is_numeric($extra_info)) {
8360
            $form->addHidden('path', $extra_info);
8361
        } elseif (is_array($extra_info)) {
8362
            $form->addHidden('path', $extra_info['path']);
8363
        }
8364
8365
        $form->addHidden('type', TOOL_THREAD);
8366
        $form->addHidden('post_time', time());
8367
        $form->setDefaults($defaults);
8368
8369
        return $form->returnForm();
8370
    }
8371
8372
    /**
8373
     * Return the HTML form to display an item (generally a dir item).
8374
     *
8375
     * @param string $item_type
8376
     * @param string $title
8377
     * @param string $action
8378
     * @param int    $id
8379
     * @param string $extra_info
8380
     *
8381
     * @throws Exception
8382
     * @throws HTML_QuickForm_Error
8383
     *
8384
     * @return string HTML form
8385
     */
8386
    public function display_item_form(
8387
        $item_type,
8388
        $title = '',
8389
        $action = 'add_item',
8390
        $id = 0,
8391
        $extra_info = 'new'
8392
    ) {
8393
        $_course = api_get_course_info();
8394
8395
        global $charset;
8396
8397
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8398
8399
        if ($id != 0 && is_array($extra_info)) {
8400
            $item_title = $extra_info['title'];
8401
            $item_description = $extra_info['description'];
8402
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8403
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8404
        } else {
8405
            $item_title = '';
8406
            $item_description = '';
8407
            $item_path_fck = '';
8408
        }
8409
8410
        if ($id != 0 && is_array($extra_info)) {
8411
            $parent = $extra_info['parent_item_id'];
8412
        } else {
8413
            $parent = 0;
8414
        }
8415
8416
        $id = intval($id);
8417
        $sql = "SELECT * FROM $tbl_lp_item
8418
                WHERE
8419
                    lp_id = ".$this->lp_id." AND
8420
                    iid != $id";
8421
8422
        if ($item_type == 'dir') {
8423
            $sql .= " AND parent_item_id = 0";
8424
        }
8425
8426
        $result = Database::query($sql);
8427
        $arrLP = [];
8428
        while ($row = Database::fetch_array($result)) {
8429
            $arrLP[] = [
8430
                'id' => $row['iid'],
8431
                'item_type' => $row['item_type'],
8432
                'title' => $row['title'],
8433
                'path' => $row['path'],
8434
                'description' => $row['description'],
8435
                'parent_item_id' => $row['parent_item_id'],
8436
                'previous_item_id' => $row['previous_item_id'],
8437
                'next_item_id' => $row['next_item_id'],
8438
                'max_score' => $row['max_score'],
8439
                'min_score' => $row['min_score'],
8440
                'mastery_score' => $row['mastery_score'],
8441
                'prerequisite' => $row['prerequisite'],
8442
                'display_order' => $row['display_order'],
8443
            ];
8444
        }
8445
8446
        $this->tree_array($arrLP);
8447
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8448
        unset($this->arrMenu);
8449
8450
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8451
8452
        $form = new FormValidator('form', 'POST', $url);
8453
        $defaults['title'] = api_html_entity_decode(
8454
            $item_title,
8455
            ENT_QUOTES,
8456
            $charset
8457
        );
8458
        $defaults['description'] = $item_description;
8459
8460
        $form->addElement('header', $title);
8461
8462
        //$arrHide = array($id);
8463
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8464
        $arrHide[0]['padding'] = 20;
8465
        $charset = api_get_system_encoding();
8466
8467
        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...
8468
            if ($action != 'add') {
8469
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8470
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8471
                ) {
8472
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8473
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8474
                    if ($parent == $arrLP[$i]['id']) {
8475
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8476
                    }
8477
                }
8478
            } else {
8479
                if ($arrLP[$i]['item_type'] == 'dir') {
8480
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8481
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8482
                    if ($parent == $arrLP[$i]['id']) {
8483
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8484
                    }
8485
                }
8486
            }
8487
        }
8488
8489
        if ($action != 'move') {
8490
            $form->addElement('text', 'title', get_lang('Title'));
8491
            $form->applyFilter('title', 'html_filter');
8492
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8493
        } else {
8494
            $form->addElement('hidden', 'title');
8495
        }
8496
8497
        $parent_select = $form->addElement(
8498
            'select',
8499
            'parent',
8500
            get_lang('Parent'),
8501
            '',
8502
            [
8503
                'id' => 'idParent',
8504
                'onchange' => "javascript: load_cbo(this.value);",
8505
            ]
8506
        );
8507
8508
        foreach ($arrHide as $key => $value) {
8509
            $parent_select->addOption(
8510
                $value['value'],
8511
                $key,
8512
                'style="padding-left:'.$value['padding'].'px;"'
8513
            );
8514
        }
8515
        if (!empty($s_selected_parent)) {
8516
            $parent_select->setSelected($s_selected_parent);
8517
        }
8518
8519
        if (is_array($arrLP)) {
8520
            reset($arrLP);
8521
        }
8522
        $arrHide = [];
8523
        // POSITION
8524
        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...
8525
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8526
                //this is the same!
8527
                if (isset($extra_info['previous_item_id']) &&
8528
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8529
                ) {
8530
                    $s_selected_position = $arrLP[$i]['id'];
8531
                } elseif ($action == 'add') {
8532
                    $s_selected_position = $arrLP[$i]['id'];
8533
                }
8534
8535
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8536
            }
8537
        }
8538
8539
        $position = $form->addElement(
8540
            'select',
8541
            'previous',
8542
            get_lang('Position'),
8543
            '',
8544
            ['id' => 'previous']
8545
        );
8546
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8547
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8548
8549
        foreach ($arrHide as $key => $value) {
8550
            $position->addOption($value['value'].'"', $key, 'style="padding-left:'.$padding.'px;"');
8551
        }
8552
8553
        if (!empty($s_selected_position)) {
8554
            $position->setSelected($s_selected_position);
8555
        }
8556
8557
        if (is_array($arrLP)) {
8558
            reset($arrLP);
8559
        }
8560
8561
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8562
8563
        //fix in order to use the tab
8564
        if ($item_type == 'dir') {
8565
            $form->addElement('hidden', 'type', 'dir');
8566
        }
8567
8568
        $extension = null;
8569
        if (!empty($item_path)) {
8570
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8571
        }
8572
8573
        //assets can't be modified
8574
        //$item_type == 'asset' ||
8575
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8576
            if ($item_type == 'sco') {
8577
                $form->addElement(
8578
                    'html',
8579
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8580
                );
8581
            }
8582
            $renderer = $form->defaultRenderer();
8583
            $renderer->setElementTemplate(
8584
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8585
                'content_lp'
8586
            );
8587
8588
            $relative_prefix = '';
8589
8590
            $editor_config = [
8591
                'ToolbarSet' => 'LearningPathDocuments',
8592
                'Width' => '100%',
8593
                'Height' => '500',
8594
                'FullPage' => true,
8595
                'CreateDocumentDir' => $relative_prefix,
8596
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8597
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8598
            ];
8599
8600
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8601
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8602
            $defaults['content_lp'] = file_get_contents($content_path);
8603
        }
8604
8605
        $form->addElement('hidden', 'type', $item_type);
8606
        $form->addElement('hidden', 'post_time', time());
8607
        $form->setDefaults($defaults);
8608
8609
        return $form->returnForm();
8610
    }
8611
8612
    /**
8613
     * @return string
8614
     */
8615
    public function getCurrentBuildingModeURL()
8616
    {
8617
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8618
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8619
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8620
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8621
8622
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8623
8624
        return $currentUrl;
8625
    }
8626
8627
    /**
8628
     * Returns the form to update or create a document.
8629
     *
8630
     * @param string $action     (add/edit)
8631
     * @param int    $id         ID of the lp_item (if already exists)
8632
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8633
     *
8634
     * @throws Exception
8635
     * @throws HTML_QuickForm_Error
8636
     *
8637
     * @return string HTML form
8638
     */
8639
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8640
    {
8641
        $course_id = api_get_course_int_id();
8642
        $_course = api_get_course_info();
8643
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8644
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8645
8646
        $no_display_edit_textarea = false;
8647
        $item_description = '';
8648
        //If action==edit document
8649
        //We don't display the document form if it's not an editable document (html or txt file)
8650
        if ($action == 'edit') {
8651
            if (is_array($extra_info)) {
8652
                $path_parts = pathinfo($extra_info['dir']);
8653
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8654
                    $no_display_edit_textarea = true;
8655
                }
8656
            }
8657
        }
8658
        $no_display_add = false;
8659
8660
        // If action==add an existing document
8661
        // We don't display the document form if it's not an editable document (html or txt file).
8662
        if ($action == 'add') {
8663
            if (is_numeric($extra_info)) {
8664
                $sql_doc = "SELECT path FROM $tbl_doc 
8665
                            WHERE c_id = $course_id AND iid = ".intval($extra_info);
8666
                $result = Database::query($sql_doc);
8667
                $path_file = Database::result($result, 0, 0);
8668
                $path_parts = pathinfo($path_file);
8669
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8670
                    $no_display_add = true;
8671
                }
8672
            }
8673
        }
8674
        if ($id != 0 && is_array($extra_info)) {
8675
            $item_title = stripslashes($extra_info['title']);
8676
            $item_description = stripslashes($extra_info['description']);
8677
            $item_terms = stripslashes($extra_info['terms']);
8678
            if (empty($item_title)) {
8679
                $path_parts = pathinfo($extra_info['path']);
8680
                $item_title = stripslashes($path_parts['filename']);
8681
            }
8682
        } elseif (is_numeric($extra_info)) {
8683
            $sql = "SELECT path, title FROM $tbl_doc
8684
                    WHERE
8685
                        c_id = ".$course_id." AND
8686
                        iid = ".intval($extra_info);
8687
            $result = Database::query($sql);
8688
            $row = Database::fetch_array($result);
8689
            $item_title = $row['title'];
8690
            $item_title = str_replace('_', ' ', $item_title);
8691
            if (empty($item_title)) {
8692
                $path_parts = pathinfo($row['path']);
8693
                $item_title = stripslashes($path_parts['filename']);
8694
            }
8695
        } else {
8696
            $item_title = '';
8697
            $item_description = '';
8698
        }
8699
        $return = '<legend>';
8700
8701
        if ($id != 0 && is_array($extra_info)) {
8702
            $parent = $extra_info['parent_item_id'];
8703
        } else {
8704
            $parent = 0;
8705
        }
8706
8707
        $sql = "SELECT * FROM $tbl_lp_item
8708
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8709
        $result = Database::query($sql);
8710
        $arrLP = [];
8711
8712
        while ($row = Database::fetch_array($result)) {
8713
            $arrLP[] = [
8714
                'id' => $row['iid'],
8715
                'item_type' => $row['item_type'],
8716
                'title' => $row['title'],
8717
                'path' => $row['path'],
8718
                'description' => $row['description'],
8719
                'parent_item_id' => $row['parent_item_id'],
8720
                'previous_item_id' => $row['previous_item_id'],
8721
                'next_item_id' => $row['next_item_id'],
8722
                'display_order' => $row['display_order'],
8723
                'max_score' => $row['max_score'],
8724
                'min_score' => $row['min_score'],
8725
                'mastery_score' => $row['mastery_score'],
8726
                'prerequisite' => $row['prerequisite'],
8727
            ];
8728
        }
8729
8730
        $this->tree_array($arrLP);
8731
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8732
        unset($this->arrMenu);
8733
8734
        if ($action == 'add') {
8735
            $return .= get_lang('CreateTheDocument');
8736
        } elseif ($action == 'move') {
8737
            $return .= get_lang('MoveTheCurrentDocument');
8738
        } else {
8739
            $return .= get_lang('EditTheCurrentDocument');
8740
        }
8741
        $return .= '</legend>';
8742
8743
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8744
            $return .= Display::return_message(
8745
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8746
                false
8747
            );
8748
        }
8749
        $form = new FormValidator(
8750
            'form',
8751
            'POST',
8752
            $this->getCurrentBuildingModeURL(),
8753
            '',
8754
            ['enctype' => 'multipart/form-data']
8755
        );
8756
        $defaults['title'] = Security::remove_XSS($item_title);
8757
        if (empty($item_title)) {
8758
            $defaults['title'] = Security::remove_XSS($item_title);
8759
        }
8760
        $defaults['description'] = $item_description;
8761
        $form->addElement('html', $return);
8762
8763
        if ($action != 'move') {
8764
            $data = $this->generate_lp_folder($_course);
8765
            if ($action != 'edit') {
8766
                $folders = DocumentManager::get_all_document_folders(
8767
                    $_course,
8768
                    0,
8769
                    true
8770
                );
8771
                DocumentManager::build_directory_selector(
8772
                    $folders,
8773
                    '',
8774
                    [],
8775
                    true,
8776
                    $form,
8777
                    'directory_parent_id'
8778
                );
8779
            }
8780
8781
            if (isset($data['id'])) {
8782
                $defaults['directory_parent_id'] = $data['id'];
8783
            }
8784
8785
            $form->addElement(
8786
                'text',
8787
                'title',
8788
                get_lang('Title'),
8789
                ['id' => 'idTitle', 'class' => 'col-md-4']
8790
            );
8791
            $form->applyFilter('title', 'html_filter');
8792
        }
8793
8794
        $arrHide[0]['value'] = $this->name;
8795
        $arrHide[0]['padding'] = 20;
8796
8797
        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...
8798
            if ($action != 'add') {
8799
                if ($arrLP[$i]['item_type'] == 'dir' &&
8800
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8801
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8802
                ) {
8803
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8804
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8805
                    if ($parent == $arrLP[$i]['id']) {
8806
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8807
                    }
8808
                }
8809
            } else {
8810
                if ($arrLP[$i]['item_type'] == 'dir') {
8811
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8812
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8813
                    if ($parent == $arrLP[$i]['id']) {
8814
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8815
                    }
8816
                }
8817
            }
8818
        }
8819
8820
        $parent_select = $form->addSelect(
8821
            'parent',
8822
            get_lang('Parent'),
8823
            [],
8824
            [
8825
                'id' => 'idParent',
8826
                'onchange' => 'javascript: load_cbo(this.value);',
8827
            ]
8828
        );
8829
8830
        $my_count = 0;
8831
        foreach ($arrHide as $key => $value) {
8832
            if ($my_count != 0) {
8833
                // The LP name is also the first section and is not in the same charset like the other sections.
8834
                $value['value'] = Security::remove_XSS($value['value']);
8835
                $parent_select->addOption(
8836
                    $value['value'],
8837
                    $key,
8838
                    'style="padding-left:'.$value['padding'].'px;"'
8839
                );
8840
            } else {
8841
                $value['value'] = Security::remove_XSS($value['value']);
8842
                $parent_select->addOption(
8843
                    $value['value'],
8844
                    $key,
8845
                    'style="padding-left:'.$value['padding'].'px;"'
8846
                );
8847
            }
8848
            $my_count++;
8849
        }
8850
8851
        if (!empty($id)) {
8852
            $parent_select->setSelected($parent);
8853
        } else {
8854
            $parent_item_id = Session::read('parent_item_id', 0);
8855
            $parent_select->setSelected($parent_item_id);
8856
        }
8857
8858
        if (is_array($arrLP)) {
8859
            reset($arrLP);
8860
        }
8861
8862
        $arrHide = [];
8863
        $s_selected_position = null;
8864
8865
        // POSITION
8866
        $lastPosition = null;
8867
8868
        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...
8869
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8870
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8871
            ) {
8872
                if ((isset($extra_info['previous_item_id']) &&
8873
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8874
                ) {
8875
                    $s_selected_position = $arrLP[$i]['id'];
8876
                }
8877
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8878
            }
8879
            $lastPosition = $arrLP[$i]['id'];
8880
        }
8881
8882
        if (empty($s_selected_position)) {
8883
            $s_selected_position = $lastPosition;
8884
        }
8885
8886
        $position = $form->addSelect(
8887
            'previous',
8888
            get_lang('Position'),
8889
            [],
8890
            ['id' => 'previous']
8891
        );
8892
        $position->addOption(get_lang('FirstPosition'), 0);
8893
8894
        foreach ($arrHide as $key => $value) {
8895
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8896
            $position->addOption(
8897
                $value['value'],
8898
                $key,
8899
                'style="padding-left:'.$padding.'px;"'
8900
            );
8901
        }
8902
        $position->setSelected($s_selected_position);
8903
8904
        if (is_array($arrLP)) {
8905
            reset($arrLP);
8906
        }
8907
8908
        if ($action != 'move') {
8909
            $id_prerequisite = 0;
8910
            if (is_array($arrLP)) {
8911
                foreach ($arrLP as $key => $value) {
8912
                    if ($value['id'] == $id) {
8913
                        $id_prerequisite = $value['prerequisite'];
8914
                        break;
8915
                    }
8916
                }
8917
            }
8918
8919
            $arrHide = [];
8920
            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...
8921
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8922
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8923
                ) {
8924
                    if (isset($extra_info['previous_item_id']) &&
8925
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8926
                    ) {
8927
                        $s_selected_position = $arrLP[$i]['id'];
8928
                    } elseif ($action == 'add') {
8929
                        $s_selected_position = $arrLP[$i]['id'];
8930
                    }
8931
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8932
                }
8933
            }
8934
8935
            if (!$no_display_add) {
8936
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8937
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8938
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8939
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8940
                ) {
8941
                    if (isset($_POST['content'])) {
8942
                        $content = stripslashes($_POST['content']);
8943
                    } elseif (is_array($extra_info)) {
8944
                        //If it's an html document or a text file
8945
                        if (!$no_display_edit_textarea) {
8946
                            $content = $this->display_document(
8947
                                $extra_info['path'],
8948
                                false,
8949
                                false
8950
                            );
8951
                        }
8952
                    } elseif (is_numeric($extra_info)) {
8953
                        $content = $this->display_document(
8954
                            $extra_info,
8955
                            false,
8956
                            false
8957
                        );
8958
                    } else {
8959
                        $content = '';
8960
                    }
8961
8962
                    if (!$no_display_edit_textarea) {
8963
                        // We need to calculate here some specific settings for the online editor.
8964
                        // The calculated settings work for documents in the Documents tool
8965
                        // (on the root or in subfolders).
8966
                        // For documents in native scorm packages it is unclear whether the
8967
                        // online editor should be activated or not.
8968
8969
                        // A new document, it is in the root of the repository.
8970
                        $relative_path = '';
8971
                        $relative_prefix = '';
8972
                        if (is_array($extra_info) && $extra_info != 'new') {
8973
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8974
                            $relative_path = explode('/', $extra_info['dir']);
8975
                            $cnt = count($relative_path) - 2;
8976
                            if ($cnt < 0) {
8977
                                $cnt = 0;
8978
                            }
8979
                            $relative_prefix = str_repeat('../', $cnt);
8980
                            $relative_path = array_slice($relative_path, 1, $cnt);
8981
                            $relative_path = implode('/', $relative_path);
8982
                            if (strlen($relative_path) > 0) {
8983
                                $relative_path = $relative_path.'/';
8984
                            }
8985
                        } else {
8986
                            $result = $this->generate_lp_folder($_course);
8987
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8988
                            $relative_prefix = '../../';
8989
                        }
8990
8991
                        $editor_config = [
8992
                            'ToolbarSet' => 'LearningPathDocuments',
8993
                            'Width' => '100%',
8994
                            'Height' => '500',
8995
                            'FullPage' => true,
8996
                            'CreateDocumentDir' => $relative_prefix,
8997
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8998
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8999
                        ];
9000
9001
                        if ($_GET['action'] == 'add_item') {
9002
                            $class = 'add';
9003
                            $text = get_lang('LPCreateDocument');
9004
                        } else {
9005
                            if ($_GET['action'] == 'edit_item') {
9006
                                $class = 'save';
9007
                                $text = get_lang('SaveDocument');
9008
                            }
9009
                        }
9010
9011
                        $form->addButtonSave($text, 'submit_button');
9012
                        $renderer = $form->defaultRenderer();
9013
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9014
                        $form->addElement('html', '<div class="editor-lp">');
9015
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9016
                        $form->addElement('html', '</div>');
9017
                        $defaults['content_lp'] = $content;
9018
                    }
9019
                } elseif (is_numeric($extra_info)) {
9020
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9021
9022
                    $return = $this->display_document($extra_info, true, true, true);
9023
                    $form->addElement('html', $return);
9024
                }
9025
            }
9026
        }
9027
        if (isset($extra_info['item_type']) &&
9028
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9029
        ) {
9030
            $parent_select->freeze();
9031
            $position->freeze();
9032
        }
9033
9034
        if ($action == 'move') {
9035
            $form->addElement('hidden', 'title', $item_title);
9036
            $form->addElement('hidden', 'description', $item_description);
9037
        }
9038
        if (is_numeric($extra_info)) {
9039
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9040
            $form->addElement('hidden', 'path', $extra_info);
9041
        } elseif (is_array($extra_info)) {
9042
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9043
            $form->addElement('hidden', 'path', $extra_info['path']);
9044
        }
9045
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9046
        $form->addElement('hidden', 'post_time', time());
9047
        $form->setDefaults($defaults);
9048
9049
        return $form->returnForm();
9050
    }
9051
9052
    /**
9053
     * Return HTML form to add/edit a link item.
9054
     *
9055
     * @param string $action     (add/edit)
9056
     * @param int    $id         Item ID if exists
9057
     * @param mixed  $extra_info
9058
     *
9059
     * @throws Exception
9060
     * @throws HTML_QuickForm_Error
9061
     *
9062
     * @return string HTML form
9063
     */
9064
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9065
    {
9066
        $course_id = api_get_course_int_id();
9067
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9068
        $tbl_link = Database::get_course_table(TABLE_LINK);
9069
9070
        if ($id != 0 && is_array($extra_info)) {
9071
            $item_title = stripslashes($extra_info['title']);
9072
            $item_description = stripslashes($extra_info['description']);
9073
            $item_url = stripslashes($extra_info['url']);
9074
        } elseif (is_numeric($extra_info)) {
9075
            $extra_info = intval($extra_info);
9076
            $sql = "SELECT title, description, url FROM ".$tbl_link."
9077
                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
9078
            $result = Database::query($sql);
9079
            $row = Database::fetch_array($result);
9080
            $item_title = $row['title'];
9081
            $item_description = $row['description'];
9082
            $item_url = $row['url'];
9083
        } else {
9084
            $item_title = '';
9085
            $item_description = '';
9086
            $item_url = '';
9087
        }
9088
9089
        $form = new FormValidator(
9090
            'edit_link',
9091
            'POST',
9092
            $this->getCurrentBuildingModeURL()
9093
        );
9094
        $defaults = [];
9095
        if ($id != 0 && is_array($extra_info)) {
9096
            $parent = $extra_info['parent_item_id'];
9097
        } else {
9098
            $parent = 0;
9099
        }
9100
9101
        $sql = "SELECT * FROM $tbl_lp_item
9102
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9103
        $result = Database::query($sql);
9104
        $arrLP = [];
9105
9106
        while ($row = Database::fetch_array($result)) {
9107
            $arrLP[] = [
9108
                'id' => $row['id'],
9109
                'item_type' => $row['item_type'],
9110
                'title' => $row['title'],
9111
                'path' => $row['path'],
9112
                'description' => $row['description'],
9113
                'parent_item_id' => $row['parent_item_id'],
9114
                'previous_item_id' => $row['previous_item_id'],
9115
                'next_item_id' => $row['next_item_id'],
9116
                'display_order' => $row['display_order'],
9117
                'max_score' => $row['max_score'],
9118
                'min_score' => $row['min_score'],
9119
                'mastery_score' => $row['mastery_score'],
9120
                'prerequisite' => $row['prerequisite'],
9121
            ];
9122
        }
9123
9124
        $this->tree_array($arrLP);
9125
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9126
        unset($this->arrMenu);
9127
9128
        if ($action == 'add') {
9129
            $legend = get_lang('CreateTheLink');
9130
        } elseif ($action == 'move') {
9131
            $legend = get_lang('MoveCurrentLink');
9132
        } else {
9133
            $legend = get_lang('EditCurrentLink');
9134
        }
9135
9136
        $form->addHeader($legend);
9137
9138
        if ($action != 'move') {
9139
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9140
            $defaults['title'] = $item_title;
9141
        }
9142
9143
        $selectParent = $form->addSelect(
9144
            'parent',
9145
            get_lang('Parent'),
9146
            [],
9147
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9148
        );
9149
        $selectParent->addOption($this->name, 0);
9150
        $arrHide = [
9151
            $id,
9152
        ];
9153
9154
        $parent_item_id = Session::read('parent_item_id', 0);
9155
9156
        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...
9157
            if ($action != 'add') {
9158
                if (
9159
                    ($arrLP[$i]['item_type'] == 'dir') &&
9160
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9161
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9162
                ) {
9163
                    $selectParent->addOption(
9164
                        $arrLP[$i]['title'],
9165
                        $arrLP[$i]['id'],
9166
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9167
                    );
9168
9169
                    if ($parent == $arrLP[$i]['id']) {
9170
                        $selectParent->setSelected($arrLP[$i]['id']);
9171
                    }
9172
                } else {
9173
                    $arrHide[] = $arrLP[$i]['id'];
9174
                }
9175
            } else {
9176
                if ($arrLP[$i]['item_type'] == 'dir') {
9177
                    $selectParent->addOption(
9178
                        $arrLP[$i]['title'],
9179
                        $arrLP[$i]['id'],
9180
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9181
                    );
9182
9183
                    if ($parent_item_id == $arrLP[$i]['id']) {
9184
                        $selectParent->setSelected($arrLP[$i]['id']);
9185
                    }
9186
                }
9187
            }
9188
        }
9189
9190
        if (is_array($arrLP)) {
9191
            reset($arrLP);
9192
        }
9193
9194
        $selectPrevious = $form->addSelect(
9195
            'previous',
9196
            get_lang('Position'),
9197
            [],
9198
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9199
        );
9200
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9201
9202
        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...
9203
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9204
                $selectPrevious->addOption(
9205
                    $arrLP[$i]['title'],
9206
                    $arrLP[$i]['id']
9207
                );
9208
9209
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9210
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9211
                } elseif ($action == 'add') {
9212
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9213
                }
9214
            }
9215
        }
9216
9217
        if ($action != 'move') {
9218
            $urlAttributes = ['class' => 'learnpath_item_form'];
9219
9220
            if (is_numeric($extra_info)) {
9221
                $urlAttributes['disabled'] = 'disabled';
9222
            }
9223
9224
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9225
            $defaults['url'] = $item_url;
9226
9227
            $id_prerequisite = 0;
9228
            if (is_array($arrLP)) {
9229
                foreach ($arrLP as $key => $value) {
9230
                    if ($value['id'] == $id) {
9231
                        $id_prerequisite = $value['prerequisite'];
9232
                        break;
9233
                    }
9234
                }
9235
            }
9236
9237
            $arrHide = [];
9238
            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...
9239
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9240
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9241
                        $s_selected_position = $arrLP[$i]['id'];
9242
                    } elseif ($action == 'add') {
9243
                        $s_selected_position = 0;
9244
                    }
9245
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9246
                }
9247
            }
9248
        }
9249
9250
        if ($action == 'add') {
9251
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9252
        } else {
9253
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9254
        }
9255
9256
        if ($action == 'move') {
9257
            $form->addHidden('title', $item_title);
9258
            $form->addHidden('description', $item_description);
9259
        }
9260
9261
        if (is_numeric($extra_info)) {
9262
            $form->addHidden('path', $extra_info);
9263
        } elseif (is_array($extra_info)) {
9264
            $form->addHidden('path', $extra_info['path']);
9265
        }
9266
        $form->addHidden('type', TOOL_LINK);
9267
        $form->addHidden('post_time', time());
9268
        $form->setDefaults($defaults);
9269
9270
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9271
    }
9272
9273
    /**
9274
     * Return HTML form to add/edit a student publication (work).
9275
     *
9276
     * @param string $action
9277
     * @param int    $id         Item ID if already exists
9278
     * @param string $extra_info
9279
     *
9280
     * @throws Exception
9281
     *
9282
     * @return string HTML form
9283
     */
9284
    public function display_student_publication_form(
9285
        $action = 'add',
9286
        $id = 0,
9287
        $extra_info = ''
9288
    ) {
9289
        $course_id = api_get_course_int_id();
9290
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9291
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9292
9293
        if ($id != 0 && is_array($extra_info)) {
9294
            $item_title = stripslashes($extra_info['title']);
9295
            $item_description = stripslashes($extra_info['description']);
9296
        } elseif (is_numeric($extra_info)) {
9297
            $extra_info = intval($extra_info);
9298
            $sql = "SELECT title, description
9299
                    FROM $tbl_publication
9300
                    WHERE c_id = $course_id AND id = ".$extra_info;
9301
9302
            $result = Database::query($sql);
9303
            $row = Database::fetch_array($result);
9304
9305
            $item_title = $row['title'];
9306
        } else {
9307
            $item_title = get_lang('Student_publication');
9308
        }
9309
9310
        if ($id != 0 && is_array($extra_info)) {
9311
            $parent = $extra_info['parent_item_id'];
9312
        } else {
9313
            $parent = 0;
9314
        }
9315
9316
        $sql = "SELECT * FROM $tbl_lp_item 
9317
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9318
        $result = Database::query($sql);
9319
        $arrLP = [];
9320
9321
        while ($row = Database::fetch_array($result)) {
9322
            $arrLP[] = [
9323
                'id' => $row['iid'],
9324
                'item_type' => $row['item_type'],
9325
                'title' => $row['title'],
9326
                'path' => $row['path'],
9327
                'description' => $row['description'],
9328
                'parent_item_id' => $row['parent_item_id'],
9329
                'previous_item_id' => $row['previous_item_id'],
9330
                'next_item_id' => $row['next_item_id'],
9331
                'display_order' => $row['display_order'],
9332
                'max_score' => $row['max_score'],
9333
                'min_score' => $row['min_score'],
9334
                'mastery_score' => $row['mastery_score'],
9335
                'prerequisite' => $row['prerequisite'],
9336
            ];
9337
        }
9338
9339
        $this->tree_array($arrLP);
9340
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9341
        unset($this->arrMenu);
9342
9343
        $form = new FormValidator('frm_student_publication', 'post', '#');
9344
9345
        if ($action == 'add') {
9346
            $form->addHeader(get_lang('Student_publication'));
9347
        } elseif ($action == 'move') {
9348
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9349
        } else {
9350
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9351
        }
9352
9353
        if ($action != 'move') {
9354
            $form->addText(
9355
                'title',
9356
                get_lang('Title'),
9357
                true,
9358
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9359
            );
9360
        }
9361
9362
        $parentSelect = $form->addSelect(
9363
            'parent',
9364
            get_lang('Parent'),
9365
            ['0' => $this->name],
9366
            [
9367
                'onchange' => 'javascript: load_cbo(this.value);',
9368
                'class' => 'learnpath_item_form',
9369
                'id' => 'idParent',
9370
            ]
9371
        );
9372
9373
        $arrHide = [$id];
9374
        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...
9375
            if ($action != 'add') {
9376
                if (
9377
                    ($arrLP[$i]['item_type'] == 'dir') &&
9378
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9379
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9380
                ) {
9381
                    $parentSelect->addOption(
9382
                        $arrLP[$i]['title'],
9383
                        $arrLP[$i]['id'],
9384
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9385
                    );
9386
9387
                    if ($parent == $arrLP[$i]['id']) {
9388
                        $parentSelect->setSelected($arrLP[$i]['id']);
9389
                    }
9390
                } else {
9391
                    $arrHide[] = $arrLP[$i]['id'];
9392
                }
9393
            } else {
9394
                if ($arrLP[$i]['item_type'] == 'dir') {
9395
                    $parentSelect->addOption(
9396
                        $arrLP[$i]['title'],
9397
                        $arrLP[$i]['id'],
9398
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9399
                    );
9400
9401
                    if ($parent == $arrLP[$i]['id']) {
9402
                        $parentSelect->setSelected($arrLP[$i]['id']);
9403
                    }
9404
                }
9405
            }
9406
        }
9407
9408
        if (is_array($arrLP)) {
9409
            reset($arrLP);
9410
        }
9411
9412
        $previousSelect = $form->addSelect(
9413
            'previous',
9414
            get_lang('Position'),
9415
            ['0' => get_lang('FirstPosition')],
9416
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9417
        );
9418
9419
        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...
9420
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9421
                $previousSelect->addOption(
9422
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9423
                    $arrLP[$i]['id']
9424
                );
9425
9426
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9427
                    $previousSelect->setSelected($arrLP[$i]['id']);
9428
                } elseif ($action == 'add') {
9429
                    $previousSelect->setSelected($arrLP[$i]['id']);
9430
                }
9431
            }
9432
        }
9433
9434
        if ($action != 'move') {
9435
            $id_prerequisite = 0;
9436
            if (is_array($arrLP)) {
9437
                foreach ($arrLP as $key => $value) {
9438
                    if ($value['id'] == $id) {
9439
                        $id_prerequisite = $value['prerequisite'];
9440
                        break;
9441
                    }
9442
                }
9443
            }
9444
            $arrHide = [];
9445
            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...
9446
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9447
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9448
                        $s_selected_position = $arrLP[$i]['id'];
9449
                    } elseif ($action == 'add') {
9450
                        $s_selected_position = 0;
9451
                    }
9452
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9453
                }
9454
            }
9455
        }
9456
9457
        if ($action == 'add') {
9458
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9459
        } else {
9460
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9461
        }
9462
9463
        if ($action == 'move') {
9464
            $form->addHidden('title', $item_title);
9465
            $form->addHidden('description', $item_description);
9466
        }
9467
9468
        if (is_numeric($extra_info)) {
9469
            $form->addHidden('path', $extra_info);
9470
        } elseif (is_array($extra_info)) {
9471
            $form->addHidden('path', $extra_info['path']);
9472
        }
9473
9474
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9475
        $form->addHidden('post_time', time());
9476
        $form->setDefaults(['title' => $item_title]);
9477
9478
        $return = '<div class="sectioncomment">';
9479
        $return .= $form->returnForm();
9480
        $return .= '</div>';
9481
9482
        return $return;
9483
    }
9484
9485
    /**
9486
     * Displays the menu for manipulating a step.
9487
     *
9488
     * @param id     $item_id
9489
     * @param string $item_type
9490
     *
9491
     * @return string
9492
     */
9493
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9494
    {
9495
        $_course = api_get_course_info();
9496
        $course_code = api_get_course_id();
9497
        $return = '<div class="actions">';
9498
        switch ($item_type) {
9499
            case 'dir':
9500
                // Commented the message cause should not show it.
9501
                //$lang = get_lang('TitleManipulateChapter');
9502
                break;
9503
            case TOOL_LP_FINAL_ITEM:
9504
            case TOOL_DOCUMENT:
9505
                // Commented the message cause should not show it.
9506
                //$lang = get_lang('TitleManipulateDocument');
9507
                break;
9508
            case TOOL_LINK:
9509
            case 'link':
9510
                // Commented the message cause should not show it.
9511
                //$lang = get_lang('TitleManipulateLink');
9512
                break;
9513
            case TOOL_QUIZ:
9514
                // Commented the message cause should not show it.
9515
                //$lang = get_lang('TitleManipulateQuiz');
9516
                break;
9517
            case TOOL_STUDENTPUBLICATION:
9518
                // Commented the message cause should not show it.
9519
                //$lang = get_lang('TitleManipulateStudentPublication');
9520
                break;
9521
        }
9522
9523
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9524
        $item_id = intval($item_id);
9525
        $sql = "SELECT * FROM $tbl_lp_item 
9526
                WHERE iid = ".$item_id;
9527
        $result = Database::query($sql);
9528
        $row = Database::fetch_assoc($result);
9529
9530
        $audio_player = null;
9531
        // We display an audio player if needed.
9532
        if (!empty($row['audio'])) {
9533
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9534
                              <a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.
9535
                              </div>';
9536
            $audio_player .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>';
9537
            $audio_player .= '<script>
9538
                var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF");
9539
                s1.addParam("allowscriptaccess","always");
9540
                s1.addParam("flashvars","file=../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'].'&autostart=true");
9541
                s1.write("container");
9542
            </script>';
9543
        }
9544
9545
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9546
9547
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9548
            $return .= Display::url(
9549
                Display::return_icon(
9550
                    'edit.png',
9551
                    get_lang('Edit'),
9552
                    [],
9553
                    ICON_SIZE_SMALL
9554
                ),
9555
                $url.'&action=edit_item&path_item='.$row['path']
9556
            );
9557
9558
            $return .= Display::url(
9559
                Display::return_icon(
9560
                    'move.png',
9561
                    get_lang('Move'),
9562
                    [],
9563
                    ICON_SIZE_SMALL
9564
                ),
9565
                $url.'&action=move_item'
9566
            );
9567
        }
9568
9569
        // Commented for now as prerequisites cannot be added to chapters.
9570
        if ($item_type != 'dir') {
9571
            $return .= Display::url(
9572
                Display::return_icon(
9573
                    'accept.png',
9574
                    get_lang('LearnpathPrerequisites'),
9575
                    [],
9576
                    ICON_SIZE_SMALL
9577
                ),
9578
                $url.'&action=edit_item_prereq'
9579
            );
9580
        }
9581
        $return .= Display::url(
9582
            Display::return_icon(
9583
                'delete.png',
9584
                get_lang('Delete'),
9585
                [],
9586
                ICON_SIZE_SMALL
9587
            ),
9588
            $url.'&action=delete_item'
9589
        );
9590
9591
        if ($item_type == TOOL_HOTPOTATOES) {
9592
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9593
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9594
        }
9595
9596
        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
9597
            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9598
            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
9599
        }
9600
9601
        $return .= '</div>';
9602
9603
        if (!empty($audio_player)) {
9604
            $return .= '<br />'.$audio_player;
9605
        }
9606
9607
        return $return;
9608
    }
9609
9610
    /**
9611
     * Creates the javascript needed for filling up the checkboxes without page reload.
9612
     *
9613
     * @return string
9614
     */
9615
    public function get_js_dropdown_array()
9616
    {
9617
        $course_id = api_get_course_int_id();
9618
        $return = 'var child_name = new Array();'."\n";
9619
        $return .= 'var child_value = new Array();'."\n\n";
9620
        $return .= 'child_name[0] = new Array();'."\n";
9621
        $return .= 'child_value[0] = new Array();'."\n\n";
9622
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9623
        $sql = "SELECT * FROM ".$tbl_lp_item."
9624
                WHERE 
9625
                    c_id = $course_id AND 
9626
                    lp_id = ".$this->lp_id." AND 
9627
                    parent_item_id = 0
9628
                ORDER BY display_order ASC";
9629
        $res_zero = Database::query($sql);
9630
        $i = 0;
9631
9632
        while ($row_zero = Database::fetch_array($res_zero)) {
9633
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9634
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9635
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9636
                }
9637
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9638
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9639
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9640
            }
9641
        }
9642
        $return .= "\n";
9643
        $sql = "SELECT * FROM $tbl_lp_item
9644
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9645
        $res = Database::query($sql);
9646
        while ($row = Database::fetch_array($res)) {
9647
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9648
                           WHERE
9649
                                c_id = ".$course_id." AND
9650
                                parent_item_id = ".$row['iid']."
9651
                           ORDER BY display_order ASC";
9652
            $res_parent = Database::query($sql_parent);
9653
            $i = 0;
9654
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9655
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9656
9657
            while ($row_parent = Database::fetch_array($res_parent)) {
9658
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9659
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9660
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9661
            }
9662
            $return .= "\n";
9663
        }
9664
9665
        return $return;
9666
    }
9667
9668
    /**
9669
     * Display the form to allow moving an item.
9670
     *
9671
     * @param int $item_id Item ID
9672
     *
9673
     * @throws Exception
9674
     * @throws HTML_QuickForm_Error
9675
     *
9676
     * @return string HTML form
9677
     */
9678
    public function display_move_item($item_id)
9679
    {
9680
        $return = '';
9681
        if (is_numeric($item_id)) {
9682
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9683
9684
            $sql = "SELECT * FROM $tbl_lp_item
9685
                    WHERE iid = $item_id";
9686
            $res = Database::query($sql);
9687
            $row = Database::fetch_array($res);
9688
9689
            switch ($row['item_type']) {
9690
                case 'dir':
9691
                case 'asset':
9692
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9693
                    $return .= $this->display_item_form(
9694
                        $row['item_type'],
9695
                        get_lang('MoveCurrentChapter'),
9696
                        'move',
9697
                        $item_id,
9698
                        $row
9699
                    );
9700
                    break;
9701
                case TOOL_DOCUMENT:
9702
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9703
                    $return .= $this->display_document_form('move', $item_id, $row);
9704
                    break;
9705
                case TOOL_LINK:
9706
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9707
                    $return .= $this->display_link_form('move', $item_id, $row);
9708
                    break;
9709
                case TOOL_HOTPOTATOES:
9710
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9711
                    $return .= $this->display_link_form('move', $item_id, $row);
9712
                    break;
9713
                case TOOL_QUIZ:
9714
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9715
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9716
                    break;
9717
                case TOOL_STUDENTPUBLICATION:
9718
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9719
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9720
                    break;
9721
                case TOOL_FORUM:
9722
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9723
                    $return .= $this->display_forum_form('move', $item_id, $row);
9724
                    break;
9725
                case TOOL_THREAD:
9726
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9727
                    $return .= $this->display_forum_form('move', $item_id, $row);
9728
                    break;
9729
            }
9730
        }
9731
9732
        return $return;
9733
    }
9734
9735
    /**
9736
     * Displays a basic form on the overview page for changing the item title and the item description.
9737
     *
9738
     * @param string $item_type
9739
     * @param string $title
9740
     * @param array  $data
9741
     *
9742
     * @throws Exception
9743
     * @throws HTML_QuickForm_Error
9744
     *
9745
     * @return string
9746
     */
9747
    public function display_item_small_form($item_type, $title = '', $data = [])
9748
    {
9749
        $url = api_get_self().'?'.api_get_cidreq().'&action=edit_item&lp_id='.$this->lp_id;
9750
        $form = new FormValidator('small_form', 'post', $url);
9751
        $form->addElement('header', $title);
9752
        $form->addElement('text', 'title', get_lang('Title'));
9753
        $form->addButtonSave(get_lang('Save'), 'submit_button');
9754
        $form->addElement('hidden', 'id', $data['id']);
9755
        $form->addElement('hidden', 'parent', $data['parent_item_id']);
9756
        $form->addElement('hidden', 'previous', $data['previous_item_id']);
9757
        $form->setDefaults(['title' => $data['title']]);
9758
9759
        return $form->toHtml();
9760
    }
9761
9762
    /**
9763
     * Return HTML form to allow prerequisites selection.
9764
     *
9765
     * @todo use FormValidator
9766
     *
9767
     * @param int Item ID
9768
     *
9769
     * @return string HTML form
9770
     */
9771
    public function display_item_prerequisites_form($item_id = 0)
9772
    {
9773
        $course_id = api_get_course_int_id();
9774
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9775
        $item_id = intval($item_id);
9776
        /* Current prerequisite */
9777
        $sql = "SELECT * FROM $tbl_lp_item
9778
                WHERE iid = $item_id";
9779
        $result = Database::query($sql);
9780
        $row = Database::fetch_array($result);
9781
        $prerequisiteId = $row['prerequisite'];
9782
        $return = '<legend>';
9783
        $return .= get_lang('AddEditPrerequisites');
9784
        $return .= '</legend>';
9785
        $return .= '<form method="POST">';
9786
        $return .= '<div class="table-responsive">';
9787
        $return .= '<table class="table table-hover">';
9788
        $return .= '<thead>';
9789
        $return .= '<tr>';
9790
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9791
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9792
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9793
        $return .= '</tr>';
9794
        $return .= '</thead>';
9795
9796
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9797
        $return .= '<tbody>';
9798
        $return .= '<tr>';
9799
        $return .= '<td colspan="3">';
9800
        $return .= '<div class="radio learnpath"><label for="idNone">';
9801
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9802
        $return .= get_lang('None').'</label>';
9803
        $return .= '</div>';
9804
        $return .= '</tr>';
9805
9806
        $sql = "SELECT * FROM $tbl_lp_item
9807
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9808
        $result = Database::query($sql);
9809
        $arrLP = [];
9810
9811
        $selectedMinScore = [];
9812
        $selectedMaxScore = [];
9813
        while ($row = Database::fetch_array($result)) {
9814
            if ($row['id'] == $item_id) {
9815
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9816
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9817
            }
9818
            $arrLP[] = [
9819
                'id' => $row['iid'],
9820
                'item_type' => $row['item_type'],
9821
                'title' => $row['title'],
9822
                'ref' => $row['ref'],
9823
                'description' => $row['description'],
9824
                'parent_item_id' => $row['parent_item_id'],
9825
                'previous_item_id' => $row['previous_item_id'],
9826
                'next_item_id' => $row['next_item_id'],
9827
                'max_score' => $row['max_score'],
9828
                'min_score' => $row['min_score'],
9829
                'mastery_score' => $row['mastery_score'],
9830
                'prerequisite' => $row['prerequisite'],
9831
                'display_order' => $row['display_order'],
9832
                'prerequisite_min_score' => $row['prerequisite_min_score'],
9833
                'prerequisite_max_score' => $row['prerequisite_max_score'],
9834
            ];
9835
        }
9836
9837
        $this->tree_array($arrLP);
9838
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9839
        unset($this->arrMenu);
9840
9841
        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...
9842
            $item = $arrLP[$i];
9843
9844
            if ($item['id'] == $item_id) {
9845
                break;
9846
            }
9847
9848
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9849
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9850
9851
            $return .= '<tr>';
9852
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9853
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9854
            $return .= '<label for="id'.$item['id'].'">';
9855
            $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'].'" />';
9856
9857
            $icon_name = str_replace(' ', '', $item['item_type']);
9858
9859
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9860
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9861
            } else {
9862
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9863
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9864
                } else {
9865
                    $return .= Display::return_icon('folder_document.png');
9866
                }
9867
            }
9868
9869
            $return .= $item['title'].'</label>';
9870
            $return .= '</div>';
9871
            $return .= '</td>';
9872
9873
            if ($item['item_type'] == TOOL_QUIZ) {
9874
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9875
                $lpItemObj = new LpItem($course_id, $item['id']);
9876
                $exercise = new Exercise($course_id);
9877
                $exercise->read($lpItemObj->path);
9878
                $lpItemObj->max_score = $exercise->get_max_score();
9879
                $lpItemObj->update();
9880
                $item['max_score'] = $lpItemObj->max_score;
9881
9882
                $return .= '<td>';
9883
                $return .= '<input 
9884
                    class="form-control" 
9885
                    size="4" maxlength="3" 
9886
                    name="min_'.$item['id'].'" 
9887
                    type="number" 
9888
                    min="0" 
9889
                    step="1" 
9890
                    max="'.$item['max_score'].'" 
9891
                    value="'.$selectedMinScoreValue.'" 
9892
                />';
9893
                $return .= '</td>';
9894
                $return .= '<td>';
9895
                $return .= '<input 
9896
                    class="form-control" 
9897
                    size="4" 
9898
                    maxlength="3" 
9899
                    name="max_'.$item['id'].'" 
9900
                    type="number" 
9901
                    min="0" 
9902
                    step="1" 
9903
                    max="'.$item['max_score'].'" 
9904
                    value="'.$selectedMaxScoreValue.'" 
9905
                />';
9906
                $return .= '</td>';
9907
            }
9908
9909
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9910
                $return .= '<td>';
9911
                $return .= '<input 
9912
                    size="4" 
9913
                    maxlength="3" 
9914
                    name="min_'.$item['id'].'" 
9915
                    type="number" 
9916
                    min="0" 
9917
                    step="1" 
9918
                    max="'.$item['max_score'].'" 
9919
                    value="'.$selectedMinScoreValue.'" 
9920
                />';
9921
                $return .= '</td>';
9922
                $return .= '<td>';
9923
                $return .= '<input 
9924
                    size="4" 
9925
                    maxlength="3" 
9926
                    name="max_'.$item['id'].'" 
9927
                    type="number" 
9928
                    min="0" 
9929
                    step="1" 
9930
                    max="'.$item['max_score'].'" 
9931
                    value="'.$selectedMaxScoreValue.'" 
9932
                />';
9933
                $return .= '</td>';
9934
            }
9935
            $return .= '</tr>';
9936
        }
9937
        $return .= '<tr>';
9938
        $return .= '</tr>';
9939
        $return .= '</tbody>';
9940
        $return .= '</table>';
9941
        $return .= '</div>';
9942
        $return .= '<div class="form-group">';
9943
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9944
            get_lang('ModifyPrerequisites').'</button>';
9945
        $return .= '</form>';
9946
9947
        return $return;
9948
    }
9949
9950
    /**
9951
     * Return HTML list to allow prerequisites selection for lp.
9952
     *
9953
     * @return string HTML form
9954
     */
9955
    public function display_lp_prerequisites_list()
9956
    {
9957
        $course_id = api_get_course_int_id();
9958
        $lp_id = $this->lp_id;
9959
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9960
9961
        // get current prerequisite
9962
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9963
        $result = Database::query($sql);
9964
        $row = Database::fetch_array($result);
9965
        $prerequisiteId = $row['prerequisite'];
9966
        $session_id = api_get_session_id();
9967
        $session_condition = api_get_session_condition($session_id, true, true);
9968
        $sql = "SELECT * FROM $tbl_lp
9969
                WHERE c_id = $course_id $session_condition
9970
                ORDER BY display_order ";
9971
        $rs = Database::query($sql);
9972
        $return = '';
9973
        $return .= '<select name="prerequisites" class="form-control">';
9974
        $return .= '<option value="0">'.get_lang('None').'</option>';
9975
        if (Database::num_rows($rs) > 0) {
9976
            while ($row = Database::fetch_array($rs)) {
9977
                if ($row['id'] == $lp_id) {
9978
                    continue;
9979
                }
9980
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9981
            }
9982
        }
9983
        $return .= '</select>';
9984
9985
        return $return;
9986
    }
9987
9988
    /**
9989
     * Creates a list with all the documents in it.
9990
     *
9991
     * @param bool $showInvisibleFiles
9992
     *
9993
     * @throws Exception
9994
     * @throws HTML_QuickForm_Error
9995
     *
9996
     * @return string
9997
     */
9998
    public function get_documents($showInvisibleFiles = false)
9999
    {
10000
        $course_info = api_get_course_info();
10001
        $sessionId = api_get_session_id();
10002
        $documentTree = DocumentManager::get_document_preview(
10003
            $course_info,
10004
            $this->lp_id,
10005
            null,
10006
            $sessionId,
10007
            true,
10008
            null,
10009
            null,
10010
            $showInvisibleFiles,
10011
            true
10012
        );
10013
10014
        $headers = [
10015
            get_lang('Files'),
10016
            get_lang('CreateTheDocument'),
10017
            get_lang('Upload'),
10018
        ];
10019
10020
        $form = new FormValidator(
10021
            'form_upload',
10022
            'POST',
10023
            $this->getCurrentBuildingModeURL(),
10024
            '',
10025
            ['enctype' => 'multipart/form-data']
10026
        );
10027
10028
        $folders = DocumentManager::get_all_document_folders(
10029
            api_get_course_info(),
10030
            0,
10031
            true
10032
        );
10033
10034
        DocumentManager::build_directory_selector(
10035
            $folders,
10036
            '',
10037
            [],
10038
            true,
10039
            $form,
10040
            'directory_parent_id'
10041
        );
10042
10043
        $group = [
10044
            $form->createElement(
10045
                'radio',
10046
                'if_exists',
10047
                get_lang("UplWhatIfFileExists"),
10048
                get_lang('UplDoNothing'),
10049
                'nothing'
10050
            ),
10051
            $form->createElement(
10052
                'radio',
10053
                'if_exists',
10054
                null,
10055
                get_lang('UplOverwriteLong'),
10056
                'overwrite'
10057
            ),
10058
            $form->createElement(
10059
                'radio',
10060
                'if_exists',
10061
                null,
10062
                get_lang('UplRenameLong'),
10063
                'rename'
10064
            ),
10065
        ];
10066
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10067
10068
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10069
        $defaultFileExistsOption = 'rename';
10070
        if (!empty($fileExistsOption)) {
10071
            $defaultFileExistsOption = $fileExistsOption;
10072
        }
10073
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10074
10075
        // Check box options
10076
        $form->addElement(
10077
            'checkbox',
10078
            'unzip',
10079
            get_lang('Options'),
10080
            get_lang('Uncompress')
10081
        );
10082
10083
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10084
        $form->addMultipleUpload($url);
10085
        $new = $this->display_document_form('add', 0);
10086
        $tabs = Display::tabs(
10087
            $headers,
10088
            [$documentTree, $new, $form->returnForm()],
10089
            'subtab'
10090
        );
10091
10092
        return $tabs;
10093
    }
10094
10095
    /**
10096
     * Creates a list with all the exercises (quiz) in it.
10097
     *
10098
     * @return string
10099
     */
10100
    public function get_exercises()
10101
    {
10102
        $course_id = api_get_course_int_id();
10103
        $session_id = api_get_session_id();
10104
        $userInfo = api_get_user_info();
10105
10106
        // New for hotpotatoes.
10107
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10108
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10109
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10110
        $condition_session = api_get_session_condition($session_id, true, true);
10111
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10112
10113
        $activeCondition = ' active <> -1 ';
10114
        if ($setting) {
10115
            $activeCondition = ' active = 1 ';
10116
        }
10117
10118
        $sql_quiz = "SELECT * FROM $tbl_quiz
10119
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10120
                     ORDER BY title ASC";
10121
10122
        $sql_hot = "SELECT * FROM $tbl_doc
10123
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10124
                     ORDER BY id ASC";
10125
10126
        $res_quiz = Database::query($sql_quiz);
10127
        $res_hot = Database::query($sql_hot);
10128
10129
        $return = '<ul class="lp_resource">';
10130
        $return .= '<li class="lp_resource_element">';
10131
        $return .= Display::return_icon('new_exercice.png');
10132
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10133
            get_lang('NewExercise').'</a>';
10134
        $return .= '</li>';
10135
10136
        $previewIcon = Display::return_icon(
10137
            'preview_view.png',
10138
            get_lang('Preview')
10139
        );
10140
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10141
10142
        // Display hotpotatoes
10143
        while ($row_hot = Database::fetch_array($res_hot)) {
10144
            $link = Display::url(
10145
                $previewIcon,
10146
                $exerciseUrl.'&file='.$row_hot['path'],
10147
                ['target' => '_blank']
10148
            );
10149
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10150
            $return .= '<a class="moved" href="#">';
10151
            $return .= Display::return_icon(
10152
                'move_everywhere.png',
10153
                get_lang('Move'),
10154
                [],
10155
                ICON_SIZE_TINY
10156
            );
10157
            $return .= '</a> ';
10158
            $return .= Display::return_icon('hotpotatoes_s.png');
10159
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10160
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10161
            $return .= '</li>';
10162
        }
10163
10164
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10165
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10166
            $title = strip_tags(
10167
                api_html_entity_decode($row_quiz['title'])
10168
            );
10169
10170
            $link = Display::url(
10171
                $previewIcon,
10172
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10173
                ['target' => '_blank']
10174
            );
10175
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10176
            $return .= '<a class="moved" href="#">';
10177
            $return .= Display::return_icon(
10178
                'move_everywhere.png',
10179
                get_lang('Move'),
10180
                [],
10181
                ICON_SIZE_TINY
10182
            );
10183
            $return .= '</a> ';
10184
            $return .= Display::return_icon(
10185
                'quiz.png',
10186
                '',
10187
                [],
10188
                ICON_SIZE_TINY
10189
            );
10190
            $sessionStar = api_get_session_image(
10191
                $row_quiz['session_id'],
10192
                $userInfo['status']
10193
            );
10194
            $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.'">'.
10195
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10196
                '</a>';
10197
10198
            $return .= '</li>';
10199
        }
10200
10201
        $return .= '</ul>';
10202
10203
        return $return;
10204
    }
10205
10206
    /**
10207
     * Creates a list with all the links in it.
10208
     *
10209
     * @return string
10210
     */
10211
    public function get_links()
10212
    {
10213
        $selfUrl = api_get_self();
10214
        $courseIdReq = api_get_cidreq();
10215
        $course = api_get_course_info();
10216
        $userInfo = api_get_user_info();
10217
10218
        $course_id = $course['real_id'];
10219
        $tbl_link = Database::get_course_table(TABLE_LINK);
10220
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10221
        $moveEverywhereIcon = Display::return_icon(
10222
            'move_everywhere.png',
10223
            get_lang('Move'),
10224
            [],
10225
            ICON_SIZE_TINY
10226
        );
10227
10228
        $session_id = api_get_session_id();
10229
        $condition_session = api_get_session_condition(
10230
            $session_id,
10231
            true,
10232
            true,
10233
            "link.session_id"
10234
        );
10235
10236
        $sql = "SELECT 
10237
                    link.id as link_id,
10238
                    link.title as link_title,
10239
                    link.session_id as link_session_id,
10240
                    link.category_id as category_id,
10241
                    link_category.category_title as category_title
10242
                FROM $tbl_link as link
10243
                LEFT JOIN $linkCategoryTable as link_category
10244
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10245
                WHERE link.c_id = ".$course_id." $condition_session
10246
                ORDER BY link_category.category_title ASC, link.title ASC";
10247
        $result = Database::query($sql);
10248
        $categorizedLinks = [];
10249
        $categories = [];
10250
10251
        while ($link = Database::fetch_array($result)) {
10252
            if (!$link['category_id']) {
10253
                $link['category_title'] = get_lang('Uncategorized');
10254
            }
10255
            $categories[$link['category_id']] = $link['category_title'];
10256
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10257
        }
10258
10259
        $linksHtmlCode =
10260
            '<script>
10261
            function toggle_tool(tool, id) {
10262
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10263
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10264
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10265
                } else {
10266
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10267
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10268
                }
10269
            }
10270
        </script>
10271
10272
        <ul class="lp_resource">
10273
            <li class="lp_resource_element">
10274
                '.Display::return_icon('linksnew.gif').'
10275
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10276
                get_lang('LinkAdd').'
10277
                </a>
10278
            </li>';
10279
10280
        foreach ($categorizedLinks as $categoryId => $links) {
10281
            $linkNodes = null;
10282
            foreach ($links as $key => $linkInfo) {
10283
                $title = $linkInfo['link_title'];
10284
                $linkSessionId = $linkInfo['link_session_id'];
10285
10286
                $link = Display::url(
10287
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10288
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10289
                    ['target' => '_blank']
10290
                );
10291
10292
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10293
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10294
                    $linkNodes .=
10295
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10296
                        <a class="moved" href="#">'.
10297
                            $moveEverywhereIcon.
10298
                        '</a>
10299
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10300
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10301
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10302
                        Security::remove_XSS($title).$sessionStar.$link.
10303
                        '</a>
10304
                    </li>';
10305
                }
10306
            }
10307
            $linksHtmlCode .=
10308
                '<li>
10309
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10310
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10311
                    align="absbottom" />
10312
                </a>
10313
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10314
            </li>
10315
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10316
        }
10317
        $linksHtmlCode .= '</ul>';
10318
10319
        return $linksHtmlCode;
10320
    }
10321
10322
    /**
10323
     * Creates a list with all the student publications in it.
10324
     *
10325
     * @return string
10326
     */
10327
    public function get_student_publications()
10328
    {
10329
        $sessionId = api_get_session_id();
10330
        $return = '<ul class="lp_resource">';
10331
        $return .= '<li class="lp_resource_element">';
10332
        $return .= Display::return_icon('works_new.gif');
10333
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10334
            get_lang('AddAssignmentPage').'</a>';
10335
        $return .= '</li>';
10336
10337
        if (empty($sessionId)) {
10338
            require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10339
            $works = getWorkListTeacher(0, 100, null, null, null);
10340
            if (!empty($works)) {
10341
                foreach ($works as $work) {
10342
                    $link = Display::url(
10343
                        Display::return_icon('preview_view.png', get_lang('Preview')),
10344
                        api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10345
                        ['target' => '_blank']
10346
                    );
10347
10348
                    $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10349
                    $return .= '<a class="moved" href="#">';
10350
                    $return .= Display::return_icon(
10351
                        'move_everywhere.png',
10352
                        get_lang('Move'),
10353
                        [],
10354
                        ICON_SIZE_TINY
10355
                    );
10356
                    $return .= '</a> ';
10357
10358
                    $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10359
                    $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.'">'.
10360
                        Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10361
                    </a>';
10362
10363
                    $return .= '</li>';
10364
                }
10365
            }
10366
        }
10367
10368
        $return .= '</ul>';
10369
10370
        return $return;
10371
    }
10372
10373
    /**
10374
     * Creates a list with all the forums in it.
10375
     *
10376
     * @return string
10377
     */
10378
    public function get_forums()
10379
    {
10380
        require_once '../forum/forumfunction.inc.php';
10381
        require_once '../forum/forumconfig.inc.php';
10382
10383
        $forumCategories = get_forum_categories();
10384
        $forumsInNoCategory = get_forums_in_category(0);
10385
        if (!empty($forumsInNoCategory)) {
10386
            $forumCategories = array_merge(
10387
                $forumCategories,
10388
                [
10389
                    [
10390
                        'cat_id' => 0,
10391
                        'session_id' => 0,
10392
                        'visibility' => 1,
10393
                        'cat_comment' => null,
10394
                    ],
10395
                ]
10396
            );
10397
        }
10398
10399
        $forumList = get_forums();
10400
        $a_forums = [];
10401
        foreach ($forumCategories as $forumCategory) {
10402
            // The forums in this category.
10403
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10404
            if (!empty($forumsInCategory)) {
10405
                foreach ($forumList as $forum) {
10406
                    if (isset($forum['forum_category']) &&
10407
                        $forum['forum_category'] == $forumCategory['cat_id']
10408
                    ) {
10409
                        $a_forums[] = $forum;
10410
                    }
10411
                }
10412
            }
10413
        }
10414
10415
        $return = '<ul class="lp_resource">';
10416
10417
        // First add link
10418
        $return .= '<li class="lp_resource_element">';
10419
        $return .= Display::return_icon('new_forum.png');
10420
        $return .= Display::url(
10421
            get_lang('CreateANewForum'),
10422
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10423
                'action' => 'add',
10424
                'content' => 'forum',
10425
                'lp_id' => $this->lp_id,
10426
            ]),
10427
            ['title' => get_lang('CreateANewForum')]
10428
        );
10429
        $return .= '</li>';
10430
10431
        $return .= '<script>
10432
            function toggle_forum(forum_id) {
10433
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10434
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10435
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10436
                } else {
10437
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10438
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10439
                }
10440
            }
10441
        </script>';
10442
10443
        foreach ($a_forums as $forum) {
10444
            if (!empty($forum['forum_id'])) {
10445
                $link = Display::url(
10446
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10447
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10448
                    ['target' => '_blank']
10449
                );
10450
10451
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10452
                $return .= '<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('forum.png', '', [], ICON_SIZE_TINY);
10456
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10457
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10458
                            </a>
10459
                            <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">'.
10460
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10461
10462
                $return .= '</li>';
10463
10464
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10465
                $a_threads = get_threads($forum['forum_id']);
10466
                if (is_array($a_threads)) {
10467
                    foreach ($a_threads as $thread) {
10468
                        $link = Display::url(
10469
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10470
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10471
                            ['target' => '_blank']
10472
                        );
10473
10474
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10475
                        $return .= '&nbsp;<a class="moved" href="#">';
10476
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10477
                        $return .= ' </a>';
10478
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10479
                        $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.'">'.
10480
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10481
                        $return .= '</li>';
10482
                    }
10483
                }
10484
                $return .= '</div>';
10485
            }
10486
        }
10487
        $return .= '</ul>';
10488
10489
        return $return;
10490
    }
10491
10492
    /**
10493
     * // TODO: The output encoding should be equal to the system encoding.
10494
     *
10495
     * Exports the learning path as a SCORM package. This is the main function that
10496
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10497
     * whole thing and returns the zip.
10498
     *
10499
     * This method needs to be called in PHP5, as it will fail with non-adequate
10500
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10501
     * you need to call it on a learnpath object.
10502
     *
10503
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10504
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10505
     * path has been modified, it should use the generic method here below.
10506
     *
10507
     * @return string Returns the zip package string, or null if error
10508
     */
10509
    public function scorm_export()
10510
    {
10511
        $_course = api_get_course_info();
10512
        $course_id = $_course['real_id'];
10513
10514
        // Remove memory and time limits as much as possible as this might be a long process...
10515
        if (function_exists('ini_set')) {
10516
            api_set_memory_limit('256M');
10517
            ini_set('max_execution_time', 600);
10518
        }
10519
10520
        // Create the zip handler (this will remain available throughout the method).
10521
        $archive_path = api_get_path(SYS_ARCHIVE_PATH);
10522
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10523
        $temp_dir_short = uniqid();
10524
        $temp_zip_dir = $archive_path.'/'.$temp_dir_short;
10525
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10526
        $zip_folder = new PclZip($temp_zip_file);
10527
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10528
        $root_path = $main_path = api_get_path(SYS_PATH);
10529
        $files_cleanup = [];
10530
10531
        // Place to temporarily stash the zip file.
10532
        // create the temp dir if it doesn't exist
10533
        // or do a cleanup before creating the zip file.
10534
        if (!is_dir($temp_zip_dir)) {
10535
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10536
        } else {
10537
            // Cleanup: Check the temp dir for old files and delete them.
10538
            $handle = opendir($temp_zip_dir);
10539
            while (false !== ($file = readdir($handle))) {
10540
                if ($file != '.' && $file != '..') {
10541
                    unlink("$temp_zip_dir/$file");
10542
                }
10543
            }
10544
            closedir($handle);
10545
        }
10546
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10547
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10548
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10549
        ) {
10550
            // Remove the possible . at the end of the path.
10551
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10552
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10553
            mkdir(
10554
                $dest_path_to_scorm_folder,
10555
                api_get_permissions_for_new_directories(),
10556
                true
10557
            );
10558
            copyr(
10559
                $current_course_path.'/scorm/'.$this->path,
10560
                $dest_path_to_scorm_folder,
10561
                ['imsmanifest'],
10562
                $zip_files
10563
            );
10564
        }
10565
10566
        // Build a dummy imsmanifest structure.
10567
        // Do not add to the zip yet (we still need it).
10568
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10569
        // Aggregation Model official document, section "2.3 Content Packaging".
10570
        // We are going to build a UTF-8 encoded manifest. Later we will recode it to the desired (and supported) encoding.
10571
        $xmldoc = new DOMDocument('1.0');
10572
        $root = $xmldoc->createElement('manifest');
10573
        $root->setAttribute('identifier', 'SingleCourseManifest');
10574
        $root->setAttribute('version', '1.1');
10575
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10576
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10577
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10578
        $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');
10579
        // Build mandatory sub-root container elements.
10580
        $metadata = $xmldoc->createElement('metadata');
10581
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10582
        $metadata->appendChild($md_schema);
10583
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10584
        $metadata->appendChild($md_schemaversion);
10585
        $root->appendChild($metadata);
10586
10587
        $organizations = $xmldoc->createElement('organizations');
10588
        $resources = $xmldoc->createElement('resources');
10589
10590
        // Build the only organization we will use in building our learnpaths.
10591
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10592
        $organization = $xmldoc->createElement('organization');
10593
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10594
        // To set the title of the SCORM entity (=organization), we take the name given
10595
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10596
        // learning path charset) as it is the encoding that defines how it is stored
10597
        // in the database. Then we convert it to HTML entities again as the "&" character
10598
        // alone is not authorized in XML (must be &amp;).
10599
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10600
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10601
        $organization->appendChild($org_title);
10602
10603
        $folder_name = 'document';
10604
10605
        // Removes the learning_path/scorm_folder path when exporting see #4841
10606
        $path_to_remove = null;
10607
        $result = $this->generate_lp_folder($_course);
10608
10609
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10610
            $path_to_remove = 'document'.$result['dir'];
10611
            $path_to_replace = $folder_name.'/';
10612
        }
10613
10614
        // Fixes chamilo scorm exports
10615
        if ($this->ref === 'chamilo_scorm_export') {
10616
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10617
        }
10618
10619
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10620
        // Always call the learnpathItem->scorm_export() method to change it to the SCORM format.
10621
        $link_updates = [];
10622
        $links_to_create = [];
10623
        //foreach ($this->items as $index => $item) {
10624
        foreach ($this->ordered_items as $index => $itemId) {
10625
            $item = $this->items[$itemId];
10626
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10627
                // Get included documents from this item.
10628
                if ($item->type === 'sco') {
10629
                    $inc_docs = $item->get_resources_from_source(
10630
                        null,
10631
                        api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()
10632
                    );
10633
                } else {
10634
                    $inc_docs = $item->get_resources_from_source();
10635
                }
10636
                // Give a child element <item> to the <organization> element.
10637
                $my_item_id = $item->get_id();
10638
                $my_item = $xmldoc->createElement('item');
10639
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10640
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10641
                $my_item->setAttribute('isvisible', 'true');
10642
                // Give a child element <title> to the <item> element.
10643
                $my_title = $xmldoc->createElement(
10644
                    'title',
10645
                    htmlspecialchars(
10646
                        api_utf8_encode($item->get_title()),
10647
                        ENT_QUOTES,
10648
                        'UTF-8'
10649
                    )
10650
                );
10651
                $my_item->appendChild($my_title);
10652
                // Give a child element <adlcp:prerequisites> to the <item> element.
10653
                $my_prereqs = $xmldoc->createElement(
10654
                    'adlcp:prerequisites',
10655
                    $this->get_scorm_prereq_string($my_item_id)
10656
                );
10657
                $my_prereqs->setAttribute('type', 'aicc_script');
10658
                $my_item->appendChild($my_prereqs);
10659
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10660
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10661
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10662
                //$xmldoc->createElement('adlcp:timelimitaction','');
10663
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10664
                //$xmldoc->createElement('adlcp:datafromlms','');
10665
                // Give a child element <adlcp:masteryscore> to the <item> element.
10666
                $my_masteryscore = $xmldoc->createElement(
10667
                    'adlcp:masteryscore',
10668
                    $item->get_mastery_score()
10669
                );
10670
                $my_item->appendChild($my_masteryscore);
10671
10672
                // Attach this item to the organization element or hits parent if there is one.
10673
                if (!empty($item->parent) && $item->parent != 0) {
10674
                    $children = $organization->childNodes;
10675
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10676
                    if (is_object($possible_parent)) {
10677
                        $possible_parent->appendChild($my_item);
10678
                    } else {
10679
                        if ($this->debug > 0) {
10680
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10681
                        }
10682
                    }
10683
                } else {
10684
                    if ($this->debug > 0) {
10685
                        error_log('No parent');
10686
                    }
10687
                    $organization->appendChild($my_item);
10688
                }
10689
10690
                // Get the path of the file(s) from the course directory root.
10691
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10692
10693
                if (!empty($path_to_remove)) {
10694
                    //From docs
10695
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10696
10697
                    //From quiz
10698
                    if ($this->ref === 'chamilo_scorm_export') {
10699
                        $path_to_remove = 'scorm/'.$this->path.'/';
10700
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10701
                    }
10702
                } else {
10703
                    $my_xml_file_path = $my_file_path;
10704
                }
10705
10706
                $my_sub_dir = dirname($my_file_path);
10707
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10708
                $my_xml_sub_dir = $my_sub_dir;
10709
                // Give a <resource> child to the <resources> element
10710
                $my_resource = $xmldoc->createElement('resource');
10711
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10712
                $my_resource->setAttribute('type', 'webcontent');
10713
                $my_resource->setAttribute('href', $my_xml_file_path);
10714
                // adlcp:scormtype can be either 'sco' or 'asset'.
10715
                if ($item->type === 'sco') {
10716
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10717
                } else {
10718
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10719
                }
10720
                // xml:base is the base directory to find the files declared in this resource.
10721
                $my_resource->setAttribute('xml:base', '');
10722
                // Give a <file> child to the <resource> element.
10723
                $my_file = $xmldoc->createElement('file');
10724
                $my_file->setAttribute('href', $my_xml_file_path);
10725
                $my_resource->appendChild($my_file);
10726
10727
                // Dependency to other files - not yet supported.
10728
                $i = 1;
10729
                if ($inc_docs) {
10730
                    foreach ($inc_docs as $doc_info) {
10731
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10732
                            continue;
10733
                        }
10734
                        $my_dep = $xmldoc->createElement('resource');
10735
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10736
                        $my_dep->setAttribute('identifier', $res_id);
10737
                        $my_dep->setAttribute('type', 'webcontent');
10738
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10739
                        $my_dep_file = $xmldoc->createElement('file');
10740
                        // Check type of URL.
10741
                        if ($doc_info[1] == 'remote') {
10742
                            // Remote file. Save url as is.
10743
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10744
                            $my_dep->setAttribute('xml:base', '');
10745
                        } elseif ($doc_info[1] === 'local') {
10746
                            switch ($doc_info[2]) {
10747
                                case 'url': // Local URL - save path as url for now, don't zip file.
10748
                                    $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10749
                                    $current_dir = dirname($abs_path);
10750
                                    $current_dir = str_replace('\\', '/', $current_dir);
10751
                                    $file_path = realpath($abs_path);
10752
                                    $file_path = str_replace('\\', '/', $file_path);
10753
                                    $my_dep_file->setAttribute('href', $file_path);
10754
                                    $my_dep->setAttribute('xml:base', '');
10755
                                    if (strstr($file_path, $main_path) !== false) {
10756
                                        // The calculated real path is really inside Chamilo's root path.
10757
                                        // Reduce file path to what's under the DocumentRoot.
10758
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10759
                                        //echo $file_path;echo '<br /><br />';
10760
                                        //error_log(__LINE__.'Reduced url path: '.$file_path, 0);
10761
                                        $zip_files_abs[] = $file_path;
10762
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10763
                                        $my_dep_file->setAttribute('href', $file_path);
10764
                                        $my_dep->setAttribute('xml:base', '');
10765
                                    } elseif (empty($file_path)) {
10766
                                        /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH)));
10767
                                        if (strpos($document_root, -1) == '/') {
10768
                                            $document_root = substr(0, -1, $document_root);
10769
                                        }*/
10770
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10771
                                        $file_path = str_replace('//', '/', $file_path);
10772
                                        if (file_exists($file_path)) {
10773
                                            $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
10774
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10775
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10776
                                            $my_dep_file->setAttribute('href', $file_path);
10777
                                            $my_dep->setAttribute('xml:base', '');
10778
                                        }
10779
                                    }
10780
                                    break;
10781
                                case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10782
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10783
                                    $my_dep->setAttribute('xml:base', '');
10784
10785
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10786
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10787
                                    $abs_img_path_without_subdir = $doc_info[0];
10788
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10789
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10790
                                    if ($pos === 0) {
10791
                                        $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp));
10792
                                    }
10793
10794
                                    //$file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir);
10795
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10796
10797
                                    $file_path = str_replace('\\', '/', $file_path);
10798
                                    $file_path = str_replace('//', '/', $file_path);
10799
10800
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10801
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10802
                                    // Check if the current document is in that path.
10803
                                    if (strstr($file_path, $cur_path) !== false) {
10804
                                        // The document is in that path, now get the relative path
10805
                                        // to the containing document.
10806
                                        $orig_file_path = dirname($cur_path.$my_file_path).'/';
10807
                                        $orig_file_path = str_replace('\\', '/', $orig_file_path);
10808
                                        $relative_path = '';
10809
                                        if (strstr($file_path, $cur_path) !== false) {
10810
                                            //$relative_path = substr($file_path, strlen($orig_file_path));
10811
                                            $relative_path = str_replace($cur_path, '', $file_path);
10812
                                            $file_path = substr($file_path, strlen($cur_path));
10813
                                        } else {
10814
                                            // This case is still a problem as it's difficult to calculate a relative path easily
10815
                                            // might still generate wrong links.
10816
                                            //$file_path = substr($file_path,strlen($cur_path));
10817
                                            // Calculate the directory path to the current file (without trailing slash).
10818
                                            $my_relative_path = dirname($file_path);
10819
                                            $my_relative_path = str_replace('\\', '/', $my_relative_path);
10820
                                            $my_relative_file = basename($file_path);
10821
                                            // Calculate the directory path to the containing file (without trailing slash).
10822
                                            $my_orig_file_path = substr($orig_file_path, 0, -1);
10823
                                            $dotdot = '';
10824
                                            $subdir = '';
10825
                                            while (strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) {
10826
                                                $my_relative_path2 = dirname($my_relative_path);
10827
                                                $my_relative_path2 = str_replace('\\', '/', $my_relative_path2);
10828
                                                $my_orig_file_path = dirname($my_orig_file_path);
10829
                                                $my_orig_file_path = str_replace('\\', '/', $my_orig_file_path);
10830
                                                $subdir = substr($my_relative_path, strlen($my_relative_path2) + 1).'/'.$subdir;
10831
                                                $dotdot += '../';
10832
                                                $my_relative_path = $my_relative_path2;
10833
                                            }
10834
                                            $relative_path = $dotdot.$subdir.$my_relative_file;
10835
                                        }
10836
                                        // Put the current document in the zip (this array is the array
10837
                                        // that will manage documents already in the course folder - relative).
10838
                                        $zip_files[] = $file_path;
10839
                                        // Update the links to the current document in the containing document (make them relative).
10840
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $relative_path];
10841
                                        $my_dep_file->setAttribute('href', $file_path);
10842
                                        $my_dep->setAttribute('xml:base', '');
10843
                                    } elseif (strstr($file_path, $main_path) !== false) {
10844
                                        // The calculated real path is really inside Chamilo's root path.
10845
                                        // Reduce file path to what's under the DocumentRoot.
10846
                                        $file_path = substr($file_path, strlen($root_path));
10847
                                        //echo $file_path;echo '<br /><br />';
10848
                                        //error_log('Reduced path: '.$file_path, 0);
10849
                                        $zip_files_abs[] = $file_path;
10850
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10851
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10852
                                        $my_dep->setAttribute('xml:base', '');
10853
                                    } elseif (empty($file_path)) {
10854
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
10855
                                        $file_path = str_replace('//', '/', $file_path);
10856
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10857
                                        $current_dir = dirname($abs_path);
10858
                                        $current_dir = str_replace('\\', '/', $current_dir);
10859
10860
                                        if (file_exists($file_path)) {
10861
                                            // We get the relative path.
10862
                                            $file_path = substr($file_path, strlen($current_dir));
10863
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10864
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10865
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10866
                                            $my_dep->setAttribute('xml:base', '');
10867
                                        }
10868
                                    }
10869
                                    break;
10870
                                case 'rel':
10871
                                    // Path relative to the current document.
10872
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10873
                                    if (substr($doc_info[0], 0, 2) == '..') {
10874
                                        // Relative path going up.
10875
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10876
                                        $current_dir = str_replace('\\', '/', $current_dir);
10877
                                        $file_path = realpath($current_dir.$doc_info[0]);
10878
                                        $file_path = str_replace('\\', '/', $file_path);
10879
                                        if (strstr($file_path, $main_path) !== false) {
10880
                                            // The calculated real path is really inside Chamilo's root path.
10881
                                            // Reduce file path to what's under the DocumentRoot.
10882
                                            $file_path = substr($file_path, strlen($root_path));
10883
                                            $zip_files_abs[] = $file_path;
10884
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10885
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10886
                                            $my_dep->setAttribute('xml:base', '');
10887
                                        }
10888
                                    } else {
10889
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10890
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10891
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10892
                                    }
10893
                                    break;
10894
                                default:
10895
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10896
                                    $my_dep->setAttribute('xml:base', '');
10897
                                    break;
10898
                            }
10899
                        }
10900
                        $my_dep->appendChild($my_dep_file);
10901
                        $resources->appendChild($my_dep);
10902
                        $dependency = $xmldoc->createElement('dependency');
10903
                        $dependency->setAttribute('identifierref', $res_id);
10904
                        $my_resource->appendChild($dependency);
10905
                        $i++;
10906
                    }
10907
                }
10908
                $resources->appendChild($my_resource);
10909
                $zip_files[] = $my_file_path;
10910
            } else {
10911
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10912
                switch ($item->type) {
10913
                    case TOOL_LINK:
10914
                        $my_item = $xmldoc->createElement('item');
10915
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10916
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10917
                        $my_item->setAttribute('isvisible', 'true');
10918
                        // Give a child element <title> to the <item> element.
10919
                        $my_title = $xmldoc->createElement(
10920
                            'title',
10921
                            htmlspecialchars(
10922
                                api_utf8_encode($item->get_title()),
10923
                                ENT_QUOTES,
10924
                                'UTF-8'
10925
                            )
10926
                        );
10927
                        $my_item->appendChild($my_title);
10928
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10929
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10930
                        $my_prereqs->setAttribute('type', 'aicc_script');
10931
                        $my_item->appendChild($my_prereqs);
10932
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10933
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10934
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10935
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10936
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10937
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10938
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10939
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10940
                        $my_item->appendChild($my_masteryscore);
10941
10942
                        // Attach this item to the organization element or its parent if there is one.
10943
                        if (!empty($item->parent) && $item->parent != 0) {
10944
                            $children = $organization->childNodes;
10945
                            for ($i = 0; $i < $children->length; $i++) {
10946
                                $item_temp = $children->item($i);
10947
                                if ($item_temp->nodeName == 'item') {
10948
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10949
                                        $item_temp->appendChild($my_item);
10950
                                    }
10951
                                }
10952
                            }
10953
                        } else {
10954
                            $organization->appendChild($my_item);
10955
                        }
10956
10957
                        $my_file_path = 'link_'.$item->get_id().'.html';
10958
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10959
                                WHERE c_id = '.$course_id.' AND id='.$item->path;
10960
                        $rs = Database::query($sql);
10961
                        if ($link = Database::fetch_array($rs)) {
10962
                            $url = $link['url'];
10963
                            $title = stripslashes($link['title']);
10964
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10965
                            $my_xml_file_path = $my_file_path;
10966
                            $my_sub_dir = dirname($my_file_path);
10967
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10968
                            $my_xml_sub_dir = $my_sub_dir;
10969
                            // Give a <resource> child to the <resources> element.
10970
                            $my_resource = $xmldoc->createElement('resource');
10971
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10972
                            $my_resource->setAttribute('type', 'webcontent');
10973
                            $my_resource->setAttribute('href', $my_xml_file_path);
10974
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10975
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10976
                            // xml:base is the base directory to find the files declared in this resource.
10977
                            $my_resource->setAttribute('xml:base', '');
10978
                            // give a <file> child to the <resource> element.
10979
                            $my_file = $xmldoc->createElement('file');
10980
                            $my_file->setAttribute('href', $my_xml_file_path);
10981
                            $my_resource->appendChild($my_file);
10982
                            $resources->appendChild($my_resource);
10983
                        }
10984
                        break;
10985
                    case TOOL_QUIZ:
10986
                        $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard.
10987
                        $exe = new Exercise();
10988
                        $exe->read($exe_id);
10989
                        $my_item = $xmldoc->createElement('item');
10990
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10991
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10992
                        $my_item->setAttribute('isvisible', 'true');
10993
                        // Give a child element <title> to the <item> element.
10994
                        $my_title = $xmldoc->createElement(
10995
                            'title',
10996
                            htmlspecialchars(
10997
                                api_utf8_encode($item->get_title()),
10998
                                ENT_QUOTES,
10999
                                'UTF-8'
11000
                            )
11001
                        );
11002
                        $my_item->appendChild($my_title);
11003
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11004
                        //$my_item->appendChild($my_max_score);
11005
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11006
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11007
                        $my_prereqs->setAttribute('type', 'aicc_script');
11008
                        $my_item->appendChild($my_prereqs);
11009
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11010
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11011
                        $my_item->appendChild($my_masteryscore);
11012
11013
                        // Attach this item to the organization element or hits parent if there is one.
11014
                        if (!empty($item->parent) && $item->parent != 0) {
11015
                            $children = $organization->childNodes;
11016
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11017
                            if ($possible_parent) {
11018
                                if ($possible_parent->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11019
                                    $possible_parent->appendChild($my_item);
11020
                                }
11021
                            }
11022
                        } else {
11023
                            $organization->appendChild($my_item);
11024
                        }
11025
11026
                        // Get the path of the file(s) from the course directory root
11027
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11028
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11029
                        // Write the contents of the exported exercise into a (big) html file
11030
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11031
                        $contents = ScormSection::export_exercise_to_scorm(
11032
                            $exe,
11033
                            true
11034
                        );
11035
11036
                        $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path;
11037
                        $res = file_put_contents($tmp_file_path, $contents);
11038
                        if ($res === false) {
11039
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11040
                        }
11041
                        $files_cleanup[] = $tmp_file_path;
11042
                        $my_xml_file_path = $my_file_path;
11043
                        $my_sub_dir = dirname($my_file_path);
11044
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11045
                        $my_xml_sub_dir = $my_sub_dir;
11046
                        // Give a <resource> child to the <resources> element.
11047
                        $my_resource = $xmldoc->createElement('resource');
11048
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11049
                        $my_resource->setAttribute('type', 'webcontent');
11050
                        $my_resource->setAttribute('href', $my_xml_file_path);
11051
11052
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11053
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11054
                        // xml:base is the base directory to find the files declared in this resource.
11055
                        $my_resource->setAttribute('xml:base', '');
11056
                        // Give a <file> child to the <resource> element.
11057
                        $my_file = $xmldoc->createElement('file');
11058
                        $my_file->setAttribute('href', $my_xml_file_path);
11059
                        $my_resource->appendChild($my_file);
11060
11061
                        // Get included docs.
11062
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11063
                        // Dependency to other files - not yet supported.
11064
                        $i = 1;
11065
                        foreach ($inc_docs as $doc_info) {
11066
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11067
                                continue;
11068
                            }
11069
                            $my_dep = $xmldoc->createElement('resource');
11070
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11071
                            $my_dep->setAttribute('identifier', $res_id);
11072
                            $my_dep->setAttribute('type', 'webcontent');
11073
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11074
                            $my_dep_file = $xmldoc->createElement('file');
11075
                            // Check type of URL.
11076
                            if ($doc_info[1] == 'remote') {
11077
                                // Remote file. Save url as is.
11078
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11079
                                $my_dep->setAttribute('xml:base', '');
11080
                            } elseif ($doc_info[1] == 'local') {
11081
                                switch ($doc_info[2]) {
11082
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11083
                                        // Save file but as local file (retrieve from URL).
11084
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11085
                                        $current_dir = dirname($abs_path);
11086
                                        $current_dir = str_replace('\\', '/', $current_dir);
11087
                                        $file_path = realpath($abs_path);
11088
                                        $file_path = str_replace('\\', '/', $file_path);
11089
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11090
                                        $my_dep->setAttribute('xml:base', '');
11091
                                        if (strstr($file_path, $main_path) !== false) {
11092
                                            // The calculated real path is really inside the chamilo root path.
11093
                                            // Reduce file path to what's under the DocumentRoot.
11094
                                            $file_path = substr($file_path, strlen($root_path));
11095
                                            $zip_files_abs[] = $file_path;
11096
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11097
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11098
                                            $my_dep->setAttribute('xml:base', '');
11099
                                        } elseif (empty($file_path)) {
11100
                                            /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH)));
11101
                                            if (strpos($document_root,-1) == '/') {
11102
                                                $document_root = substr(0, -1, $document_root);
11103
                                            }*/
11104
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11105
                                            $file_path = str_replace('//', '/', $file_path);
11106
                                            if (file_exists($file_path)) {
11107
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11108
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11109
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => 'document/'.$file_path];
11110
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11111
                                                $my_dep->setAttribute('xml:base', '');
11112
                                            }
11113
                                        }
11114
                                        break;
11115
                                    case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11116
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11117
                                        $current_dir = str_replace('\\', '/', $current_dir);
11118
                                        $file_path = realpath($doc_info[0]);
11119
                                        $file_path = str_replace('\\', '/', $file_path);
11120
                                        $my_dep_file->setAttribute('href', $file_path);
11121
                                        $my_dep->setAttribute('xml:base', '');
11122
11123
                                        if (strstr($file_path, $main_path) !== false) {
11124
                                            // The calculated real path is really inside the chamilo root path.
11125
                                            // Reduce file path to what's under the DocumentRoot.
11126
                                            $file_path = substr($file_path, strlen($root_path));
11127
                                            $zip_files_abs[] = $file_path;
11128
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11129
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11130
                                            $my_dep->setAttribute('xml:base', '');
11131
                                        } elseif (empty($file_path)) {
11132
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0];
11133
                                            $file_path = str_replace('//', '/', $file_path);
11134
                                            if (file_exists($file_path)) {
11135
                                                $file_path = substr($file_path, strlen($current_dir)); // We get the relative path.
11136
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11137
                                                $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11138
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11139
                                                $my_dep->setAttribute('xml:base', '');
11140
                                            }
11141
                                        }
11142
                                        break;
11143
                                    case 'rel':
11144
                                        // Path relative to the current document. Save xml:base as current document's
11145
                                        // directory and save file in zip as subdir.file_path
11146
                                        if (substr($doc_info[0], 0, 2) == '..') {
11147
                                            // Relative path going up.
11148
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11149
                                            $current_dir = str_replace('\\', '/', $current_dir);
11150
                                            $file_path = realpath($current_dir.$doc_info[0]);
11151
                                            $file_path = str_replace('\\', '/', $file_path);
11152
                                            //error_log($file_path.' <-> '.$main_path, 0);
11153
                                            if (strstr($file_path, $main_path) !== false) {
11154
                                                // The calculated real path is really inside Chamilo's root path.
11155
                                                // Reduce file path to what's under the DocumentRoot.
11156
11157
                                                $file_path = substr($file_path, strlen($root_path));
11158
                                                $file_path_dest = $file_path;
11159
11160
                                                // File path is courses/CHAMILO/document/....
11161
                                                $info_file_path = explode('/', $file_path);
11162
                                                if ($info_file_path[0] == 'courses') {
11163
                                                    // Add character "/" in file path.
11164
                                                    $file_path_dest = 'document/'.$file_path;
11165
                                                }
11166
11167
                                                //error_log('Reduced path: '.$file_path, 0);
11168
                                                $zip_files_abs[] = $file_path;
11169
11170
                                                $link_updates[$my_file_path][] = [
11171
                                                    'orig' => $doc_info[0],
11172
                                                    'dest' => $file_path_dest,
11173
                                                ];
11174
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11175
                                                $my_dep->setAttribute('xml:base', '');
11176
                                            }
11177
                                        } else {
11178
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11179
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11180
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11181
                                        }
11182
                                        break;
11183
                                    default:
11184
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11185
                                        $my_dep->setAttribute('xml:base', '');
11186
                                        break;
11187
                                }
11188
                            }
11189
                            $my_dep->appendChild($my_dep_file);
11190
                            $resources->appendChild($my_dep);
11191
                            $dependency = $xmldoc->createElement('dependency');
11192
                            $dependency->setAttribute('identifierref', $res_id);
11193
                            $my_resource->appendChild($dependency);
11194
                            $i++;
11195
                        }
11196
                        $resources->appendChild($my_resource);
11197
                        $zip_files[] = $my_file_path;
11198
                        break;
11199
                    default:
11200
                        // Get the path of the file(s) from the course directory root
11201
                        $my_file_path = 'non_exportable.html';
11202
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11203
                        $my_xml_file_path = $my_file_path;
11204
                        $my_sub_dir = dirname($my_file_path);
11205
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11206
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11207
                        $my_xml_sub_dir = $my_sub_dir;
11208
                        // Give a <resource> child to the <resources> element.
11209
                        $my_resource = $xmldoc->createElement('resource');
11210
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11211
                        $my_resource->setAttribute('type', 'webcontent');
11212
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11213
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11214
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11215
                        // xml:base is the base directory to find the files declared in this resource.
11216
                        $my_resource->setAttribute('xml:base', '');
11217
                        // Give a <file> child to the <resource> element.
11218
                        $my_file = $xmldoc->createElement('file');
11219
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11220
                        $my_resource->appendChild($my_file);
11221
                        $resources->appendChild($my_resource);
11222
11223
                        break;
11224
                }
11225
            }
11226
        }
11227
        $organizations->appendChild($organization);
11228
        $root->appendChild($organizations);
11229
        $root->appendChild($resources);
11230
        $xmldoc->appendChild($root);
11231
11232
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11233
11234
        // TODO: Add a readme file here, with a short description and a link to the Reload player
11235
        // then add the file to the zip, then destroy the file (this is done automatically).
11236
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11237
        foreach ($zip_files as $file_path) {
11238
            if (empty($file_path)) {
11239
                continue;
11240
            }
11241
11242
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11243
            $dest_file = $archive_path.$temp_dir_short.'/'.$file_path;
11244
11245
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11246
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11247
            }
11248
            $this->create_path($dest_file);
11249
            @copy($filePath, $dest_file);
11250
11251
            // Check if the file needs a link update.
11252
            if (in_array($file_path, array_keys($link_updates))) {
11253
                $string = file_get_contents($dest_file);
11254
                unlink($dest_file);
11255
                foreach ($link_updates[$file_path] as $old_new) {
11256
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11257
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11258
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11259
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11260
                    if (substr($old_new['dest'], -3) == 'flv' &&
11261
                        substr($old_new['dest'], 0, 5) == 'main/'
11262
                    ) {
11263
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11264
                    } elseif (substr($old_new['dest'], -3) == 'flv' &&
11265
                        substr($old_new['dest'], 0, 6) == 'video/'
11266
                    ) {
11267
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11268
                    }
11269
                    //Fix to avoid problems with default_course_document
11270
                    if (strpos("main/default_course_document", $old_new['dest'] === false)) {
11271
                        //$newDestination = str_replace('document/', $mult.'document/', $old_new['dest']);
11272
                        $newDestination = $old_new['dest'];
11273
                    } else {
11274
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11275
                    }
11276
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11277
11278
                    // Add files inside the HTMLs
11279
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11280
                    $destinationFile = $archive_path.$temp_dir_short.'/'.$old_new['dest'];
11281
                    if (file_exists($sys_course_path.$new_path)) {
11282
                        copy($sys_course_path.$new_path, $destinationFile);
11283
                    }
11284
                }
11285
                file_put_contents($dest_file, $string);
11286
            }
11287
11288
            if (file_exists($filePath) && $copyAll) {
11289
                $extension = $this->get_extension($filePath);
11290
                if (in_array($extension, ['html', 'html'])) {
11291
                    $containerOrigin = dirname($filePath);
11292
                    $containerDestination = dirname($dest_file);
11293
11294
                    $finder = new Finder();
11295
                    $finder->files()->in($containerOrigin)
11296
                        ->notName('*_DELETED_*')
11297
                        ->exclude('share_folder')
11298
                        ->exclude('chat_files')
11299
                        ->exclude('certificates')
11300
                    ;
11301
11302
                    if (is_dir($containerOrigin) &&
11303
                        is_dir($containerDestination)
11304
                    ) {
11305
                        $fs = new Filesystem();
11306
                        $fs->mirror(
11307
                            $containerOrigin,
11308
                            $containerDestination,
11309
                            $finder
11310
                        );
11311
                    }
11312
                }
11313
            }
11314
        }
11315
11316
        foreach ($zip_files_abs as $file_path) {
11317
            if (empty($file_path)) {
11318
                continue;
11319
            }
11320
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11321
                continue;
11322
            }
11323
11324
            $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path;
11325
            $this->create_path($dest_file);
11326
            copy($main_path.$file_path, $dest_file);
11327
            // Check if the file needs a link update.
11328
            if (in_array($file_path, array_keys($link_updates))) {
11329
                $string = file_get_contents($dest_file);
11330
                unlink($dest_file);
11331
                foreach ($link_updates[$file_path] as $old_new) {
11332
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11333
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11334
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11335
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11336
                    if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') {
11337
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11338
                    }
11339
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11340
                }
11341
                file_put_contents($dest_file, $string);
11342
            }
11343
        }
11344
11345
        if (is_array($links_to_create)) {
11346
            foreach ($links_to_create as $file => $link) {
11347
                $file_content = '<!DOCTYPE html><head>
11348
                                <meta charset="'.api_get_language_isocode().'" />
11349
                                <title>'.$link['title'].'</title>
11350
                                </head>
11351
                                <body dir="'.api_get_text_direction().'">
11352
                                <div style="text-align:center">
11353
                                <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11354
                                </body>
11355
                                </html>';
11356
                file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content);
11357
            }
11358
        }
11359
11360
        // Add non exportable message explanation.
11361
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11362
        $file_content = '<!DOCTYPE html><head>
11363
                        <meta charset="'.api_get_language_isocode().'" />
11364
                        <title>'.$lang_not_exportable.'</title>
11365
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11366
                        </head>
11367
                        <body dir="'.api_get_text_direction().'">';
11368
        $file_content .=
11369
            <<<EOD
11370
                    <style>
11371
            .error-message {
11372
                font-family: arial, verdana, helvetica, sans-serif;
11373
                border-width: 1px;
11374
                border-style: solid;
11375
                left: 50%;
11376
                margin: 10px auto;
11377
                min-height: 30px;
11378
                padding: 5px;
11379
                right: 50%;
11380
                width: 500px;
11381
                background-color: #FFD1D1;
11382
                border-color: #FF0000;
11383
                color: #000;
11384
            }
11385
        </style>
11386
    <body>
11387
        <div class="error-message">
11388
            $lang_not_exportable
11389
        </div>
11390
    </body>
11391
</html>
11392
EOD;
11393
        if (!is_dir($archive_path.$temp_dir_short.'/document')) {
11394
            @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11395
        }
11396
        file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content);
11397
11398
        // Add the extra files that go along with a SCORM package.
11399
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11400
11401
        $fs = new Filesystem();
11402
        $fs->mirror($main_code_path, $archive_path.$temp_dir_short);
11403
11404
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11405
        $manifest = @$xmldoc->saveXML();
11406
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11407
        file_put_contents($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11408
        $zip_folder->add(
11409
            $archive_path.'/'.$temp_dir_short,
11410
            PCLZIP_OPT_REMOVE_PATH,
11411
            $archive_path.'/'.$temp_dir_short.'/'
11412
        );
11413
11414
        // Clean possible temporary files.
11415
        foreach ($files_cleanup as $file) {
11416
            $res = unlink($file);
11417
            if ($res === false) {
11418
                error_log(
11419
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11420
                    0
11421
                );
11422
            }
11423
        }
11424
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11425
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11426
    }
11427
11428
    /**
11429
     * @param int $lp_id
11430
     *
11431
     * @return bool
11432
     */
11433
    public function scorm_export_to_pdf($lp_id)
11434
    {
11435
        $lp_id = intval($lp_id);
11436
        $files_to_export = [];
11437
        $course_data = api_get_course_info($this->cc);
11438
        if (!empty($course_data)) {
11439
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11440
            $list = self::get_flat_ordered_items_list($lp_id);
11441
            if (!empty($list)) {
11442
                foreach ($list as $item_id) {
11443
                    $item = $this->items[$item_id];
11444
                    switch ($item->type) {
11445
                        case 'document':
11446
                            //Getting documents from a LP with chamilo documents
11447
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11448
                            // Try loading document from the base course.
11449
                            if (empty($file_data) && !empty($sessionId)) {
11450
                                $file_data = DocumentManager::get_document_data_by_id(
11451
                                    $item->path,
11452
                                    $this->cc,
11453
                                    false,
11454
                                    0
11455
                                );
11456
                            }
11457
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11458
                            if (file_exists($file_path)) {
11459
                                $files_to_export[] = [
11460
                                    'title' => $item->get_title(),
11461
                                    'path' => $file_path,
11462
                                ];
11463
                            }
11464
                            break;
11465
                        case 'asset': //commes from a scorm package generated by chamilo
11466
                        case 'sco':
11467
                            $file_path = $scorm_path.'/'.$item->path;
11468
                            if (file_exists($file_path)) {
11469
                                $files_to_export[] = [
11470
                                    'title' => $item->get_title(),
11471
                                    'path' => $file_path,
11472
                                ];
11473
                            }
11474
                            break;
11475
                        case 'dir':
11476
                            $files_to_export[] = [
11477
                                'title' => $item->get_title(),
11478
                                'path' => null,
11479
                            ];
11480
                            break;
11481
                    }
11482
                }
11483
            }
11484
            $pdf = new PDF();
11485
            $result = $pdf->html_to_pdf(
11486
                $files_to_export,
11487
                $this->name,
11488
                $this->cc,
11489
                true
11490
            );
11491
11492
            return $result;
11493
        }
11494
11495
        return false;
11496
    }
11497
11498
    /**
11499
     * Temp function to be moved in main_api or the best place around for this.
11500
     * Creates a file path if it doesn't exist.
11501
     *
11502
     * @param string $path
11503
     */
11504
    public function create_path($path)
11505
    {
11506
        $path_bits = explode('/', dirname($path));
11507
11508
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11509
        $path_built = IS_WINDOWS_OS ? '' : '/';
11510
        foreach ($path_bits as $bit) {
11511
            if (!empty($bit)) {
11512
                $new_path = $path_built.$bit;
11513
                if (is_dir($new_path)) {
11514
                    $path_built = $new_path.'/';
11515
                } else {
11516
                    mkdir($new_path, api_get_permissions_for_new_directories());
11517
                    $path_built = $new_path.'/';
11518
                }
11519
            }
11520
        }
11521
    }
11522
11523
    /**
11524
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11525
     *
11526
     * @return bool The results of the unlink function, or false if there was no image to start with
11527
     */
11528
    public function delete_lp_image()
11529
    {
11530
        $img = $this->get_preview_image();
11531
        if ($img != '') {
11532
            $del_file = $this->get_preview_image_path(null, 'sys');
11533
            if (isset($del_file) && file_exists($del_file)) {
11534
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11535
                if (file_exists($del_file_2)) {
11536
                    unlink($del_file_2);
11537
                }
11538
                $this->set_preview_image('');
11539
11540
                return @unlink($del_file);
11541
            }
11542
        }
11543
11544
        return false;
11545
    }
11546
11547
    /**
11548
     * Uploads an author image to the upload/learning_path/images directory.
11549
     *
11550
     * @param array    The image array, coming from the $_FILES superglobal
11551
     *
11552
     * @return bool True on success, false on error
11553
     */
11554
    public function upload_image($image_array)
11555
    {
11556
        if (!empty($image_array['name'])) {
11557
            $upload_ok = process_uploaded_file($image_array);
11558
            $has_attachment = true;
11559
        }
11560
11561
        if ($upload_ok && $has_attachment) {
11562
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11563
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11564
            $updir = $sys_course_path.$courseDir;
11565
            // Try to add an extension to the file if it hasn't one.
11566
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11567
11568
            if (filter_extension($new_file_name)) {
11569
                $file_extension = explode('.', $image_array['name']);
11570
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
11571
                $filename = uniqid('');
11572
                $new_file_name = $filename.'.'.$file_extension;
11573
                $new_path = $updir.'/'.$new_file_name;
11574
11575
                // Resize the image.
11576
                $temp = new Image($image_array['tmp_name']);
11577
                $temp->resize(104);
11578
                $result = $temp->send_image($new_path);
11579
11580
                // Storing the image filename.
11581
                if ($result) {
11582
                    $this->set_preview_image($new_file_name);
11583
11584
                    //Resize to 64px to use on course homepage
11585
                    $temp->resize(64);
11586
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11587
11588
                    return true;
11589
                }
11590
            }
11591
        }
11592
11593
        return false;
11594
    }
11595
11596
    /**
11597
     * @param int    $lp_id
11598
     * @param string $status
11599
     */
11600
    public function set_autolaunch($lp_id, $status)
11601
    {
11602
        $course_id = api_get_course_int_id();
11603
        $lp_id = intval($lp_id);
11604
        $status = intval($status);
11605
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11606
11607
        // Setting everything to autolaunch = 0
11608
        $attributes['autolaunch'] = 0;
11609
        $where = [
11610
            'session_id = ? AND c_id = ? ' => [
11611
                api_get_session_id(),
11612
                $course_id,
11613
            ],
11614
        ];
11615
        Database::update($lp_table, $attributes, $where);
11616
        if ($status == 1) {
11617
            //Setting my lp_id to autolaunch = 1
11618
            $attributes['autolaunch'] = 1;
11619
            $where = [
11620
                'iid = ? AND session_id = ? AND c_id = ?' => [
11621
                    $lp_id,
11622
                    api_get_session_id(),
11623
                    $course_id,
11624
                ],
11625
            ];
11626
            Database::update($lp_table, $attributes, $where);
11627
        }
11628
    }
11629
11630
    /**
11631
     * Gets previous_item_id for the next element of the lp_item table.
11632
     *
11633
     * @author Isaac flores paz
11634
     *
11635
     * @return int Previous item ID
11636
     */
11637
    public function select_previous_item_id()
11638
    {
11639
        $course_id = api_get_course_int_id();
11640
        if ($this->debug > 0) {
11641
            error_log('In learnpath::select_previous_item_id()', 0);
11642
        }
11643
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11644
11645
        // Get the max order of the items
11646
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11647
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11648
        $rs_max_order = Database::query($sql);
11649
        $row_max_order = Database::fetch_object($rs_max_order);
11650
        $max_order = $row_max_order->display_order;
11651
        // Get the previous item ID
11652
        $sql = "SELECT iid as previous FROM $table_lp_item
11653
                WHERE 
11654
                    c_id = $course_id AND 
11655
                    lp_id = ".$this->lp_id." AND 
11656
                    display_order = '$max_order' ";
11657
        $rs_max = Database::query($sql);
11658
        $row_max = Database::fetch_object($rs_max);
11659
11660
        // Return the previous item ID
11661
        return $row_max->previous;
11662
    }
11663
11664
    /**
11665
     * Copies an LP.
11666
     */
11667
    public function copy()
11668
    {
11669
        // Course builder
11670
        $cb = new CourseBuilder();
11671
11672
        //Setting tools that will be copied
11673
        $cb->set_tools_to_build(['learnpaths']);
11674
11675
        //Setting elements that will be copied
11676
        $cb->set_tools_specific_id_list(
11677
            ['learnpaths' => [$this->lp_id]]
11678
        );
11679
11680
        $course = $cb->build();
11681
11682
        //Course restorer
11683
        $course_restorer = new CourseRestorer($course);
11684
        $course_restorer->set_add_text_in_items(true);
11685
        $course_restorer->set_tool_copy_settings(
11686
            ['learnpaths' => ['reset_dates' => true]]
11687
        );
11688
        $course_restorer->restore(
11689
            api_get_course_id(),
11690
            api_get_session_id(),
11691
            false,
11692
            false
11693
        );
11694
    }
11695
11696
    /**
11697
     * Verify document size.
11698
     *
11699
     * @param string $s
11700
     *
11701
     * @return bool
11702
     */
11703
    public static function verify_document_size($s)
11704
    {
11705
        $post_max = ini_get('post_max_size');
11706
        if (substr($post_max, -1, 1) == 'M') {
11707
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11708
        } elseif (substr($post_max, -1, 1) == 'G') {
11709
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11710
        }
11711
        $upl_max = ini_get('upload_max_filesize');
11712
        if (substr($upl_max, -1, 1) == 'M') {
11713
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11714
        } elseif (substr($upl_max, -1, 1) == 'G') {
11715
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11716
        }
11717
        $documents_total_space = DocumentManager::documents_total_space();
11718
        $course_max_space = DocumentManager::get_course_quota();
11719
        $total_size = filesize($s) + $documents_total_space;
11720
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11721
            return true;
11722
        } else {
11723
            return false;
11724
        }
11725
    }
11726
11727
    /**
11728
     * Clear LP prerequisites.
11729
     */
11730
    public function clear_prerequisites()
11731
    {
11732
        $course_id = $this->get_course_int_id();
11733
        if ($this->debug > 0) {
11734
            error_log('In learnpath::clear_prerequisites()', 0);
11735
        }
11736
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11737
        $lp_id = $this->get_id();
11738
        //Cleaning prerequisites
11739
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11740
                WHERE c_id = $course_id AND lp_id = $lp_id";
11741
        Database::query($sql);
11742
11743
        //Cleaning mastery score for exercises
11744
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11745
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11746
        Database::query($sql);
11747
    }
11748
11749
    public function set_previous_step_as_prerequisite_for_all_items()
11750
    {
11751
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11752
        $course_id = $this->get_course_int_id();
11753
        $lp_id = $this->get_id();
11754
11755
        if (!empty($this->items)) {
11756
            $previous_item_id = null;
11757
            $previous_item_max = 0;
11758
            $previous_item_type = null;
11759
            $last_item_not_dir = null;
11760
            $last_item_not_dir_type = null;
11761
            $last_item_not_dir_max = null;
11762
11763
            foreach ($this->ordered_items as $itemId) {
11764
                $item = $this->getItem($itemId);
11765
                // if there was a previous item... (otherwise jump to set it)
11766
                if (!empty($previous_item_id)) {
11767
                    $current_item_id = $item->get_id(); //save current id
11768
                    if ($item->get_type() != 'dir') {
11769
                        // Current item is not a folder, so it qualifies to get a prerequisites
11770
                        if ($last_item_not_dir_type == 'quiz') {
11771
                            // if previous is quiz, mark its max score as default score to be achieved
11772
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11773
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11774
                            Database::query($sql);
11775
                        }
11776
                        // now simply update the prerequisite to set it to the last non-chapter item
11777
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11778
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11779
                        Database::query($sql);
11780
                        // record item as 'non-chapter' reference
11781
                        $last_item_not_dir = $item->get_id();
11782
                        $last_item_not_dir_type = $item->get_type();
11783
                        $last_item_not_dir_max = $item->get_max();
11784
                    }
11785
                } else {
11786
                    if ($item->get_type() != 'dir') {
11787
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11788
                        $last_item_not_dir = $item->get_id();
11789
                        $last_item_not_dir_type = $item->get_type();
11790
                        $last_item_not_dir_max = $item->get_max();
11791
                    }
11792
                }
11793
                // Saving the item as "previous item" for the next loop
11794
                $previous_item_id = $item->get_id();
11795
                $previous_item_max = $item->get_max();
11796
                $previous_item_type = $item->get_type();
11797
            }
11798
        }
11799
    }
11800
11801
    /**
11802
     * @param array $params
11803
     *
11804
     * @throws \Doctrine\ORM\OptimisticLockException
11805
     */
11806
    public static function createCategory($params)
11807
    {
11808
        $em = Database::getManager();
11809
        $item = new CLpCategory();
11810
        $item->setName($params['name']);
11811
        $item->setCId($params['c_id']);
11812
        $em->persist($item);
11813
        $em->flush();
11814
    }
11815
11816
    /**
11817
     * @param array $params
11818
     *
11819
     * @throws \Doctrine\ORM\ORMException
11820
     * @throws \Doctrine\ORM\OptimisticLockException
11821
     * @throws \Doctrine\ORM\TransactionRequiredException
11822
     */
11823
    public static function updateCategory($params)
11824
    {
11825
        $em = Database::getManager();
11826
        /** @var CLpCategory $item */
11827
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11828
        if ($item) {
11829
            $item->setName($params['name']);
11830
            $em->merge($item);
11831
            $em->flush();
11832
        }
11833
    }
11834
11835
    /**
11836
     * @param int $id
11837
     *
11838
     * @throws \Doctrine\ORM\ORMException
11839
     * @throws \Doctrine\ORM\OptimisticLockException
11840
     * @throws \Doctrine\ORM\TransactionRequiredException
11841
     */
11842
    public static function moveUpCategory($id)
11843
    {
11844
        $id = (int) $id;
11845
        $em = Database::getManager();
11846
        /** @var CLpCategory $item */
11847
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11848
        if ($item) {
11849
            $position = $item->getPosition() - 1;
11850
            $item->setPosition($position);
11851
            $em->persist($item);
11852
            $em->flush();
11853
        }
11854
    }
11855
11856
    /**
11857
     * @param int $id
11858
     *
11859
     * @throws \Doctrine\ORM\ORMException
11860
     * @throws \Doctrine\ORM\OptimisticLockException
11861
     * @throws \Doctrine\ORM\TransactionRequiredException
11862
     */
11863
    public static function moveDownCategory($id)
11864
    {
11865
        $id = (int) $id;
11866
        $em = Database::getManager();
11867
        /** @var CLpCategory $item */
11868
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11869
        if ($item) {
11870
            $position = $item->getPosition() + 1;
11871
            $item->setPosition($position);
11872
            $em->persist($item);
11873
            $em->flush();
11874
        }
11875
    }
11876
11877
    /**
11878
     * @param int $courseId
11879
     *
11880
     * @throws \Doctrine\ORM\Query\QueryException
11881
     *
11882
     * @return int|mixed
11883
     */
11884
    public static function getCountCategories($courseId)
11885
    {
11886
        if (empty($course_id)) {
11887
            return 0;
11888
        }
11889
        $em = Database::getManager();
11890
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11891
        $query->setParameter('id', $courseId);
11892
11893
        return $query->getSingleScalarResult();
11894
    }
11895
11896
    /**
11897
     * @param int $courseId
11898
     *
11899
     * @return mixed
11900
     */
11901
    public static function getCategories($courseId)
11902
    {
11903
        $em = Database::getManager();
11904
        //Default behaviour
11905
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11906
            array('cId' => $course_id),
11907
            array('name' => 'ASC')
11908
        );*/
11909
11910
        // Using doctrine extensions
11911
        /** @var SortableRepository $repo */
11912
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11913
        $items = $repo
11914
            ->getBySortableGroupsQuery(['cId' => $courseId])
11915
            ->getResult();
11916
11917
        return $items;
11918
    }
11919
11920
    /**
11921
     * @param int $id
11922
     *
11923
     * @throws \Doctrine\ORM\ORMException
11924
     * @throws \Doctrine\ORM\OptimisticLockException
11925
     * @throws \Doctrine\ORM\TransactionRequiredException
11926
     *
11927
     * @return CLpCategory
11928
     */
11929
    public static function getCategory($id)
11930
    {
11931
        $id = (int) $id;
11932
        $em = Database::getManager();
11933
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11934
11935
        return $item;
11936
    }
11937
11938
    /**
11939
     * @param int $courseId
11940
     *
11941
     * @return array
11942
     */
11943
    public static function getCategoryByCourse($courseId)
11944
    {
11945
        $em = Database::getManager();
11946
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11947
            ['cId' => $courseId]
11948
        );
11949
11950
        return $items;
11951
    }
11952
11953
    /**
11954
     * @param int $id
11955
     *
11956
     * @throws \Doctrine\ORM\ORMException
11957
     * @throws \Doctrine\ORM\OptimisticLockException
11958
     * @throws \Doctrine\ORM\TransactionRequiredException
11959
     *
11960
     * @return mixed
11961
     */
11962
    public static function deleteCategory($id)
11963
    {
11964
        $em = Database::getManager();
11965
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11966
        if ($item) {
11967
            $courseId = $item->getCId();
11968
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11969
            $query->setParameter('id', $courseId);
11970
            $query->setParameter('catId', $item->getId());
11971
            $lps = $query->getResult();
11972
11973
            // Setting category = 0.
11974
            if ($lps) {
11975
                foreach ($lps as $lpItem) {
11976
                    $lpItem->setCategoryId(0);
11977
                }
11978
            }
11979
11980
            // Removing category.
11981
            $em->remove($item);
11982
            $em->flush();
11983
11984
            $courseInfo = api_get_course_info_by_id($courseId);
11985
            $sessionId = api_get_session_id();
11986
11987
            // Delete link tool
11988
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11989
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11990
            // Delete tools
11991
            $sql = "DELETE FROM $tbl_tool
11992
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11993
            Database::query($sql);
11994
        }
11995
    }
11996
11997
    /**
11998
     * @param int  $courseId
11999
     * @param bool $addSelectOption
12000
     *
12001
     * @return mixed
12002
     */
12003
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12004
    {
12005
        $items = self::getCategoryByCourse($courseId);
12006
        $cats = [];
12007
        if ($addSelectOption) {
12008
            $cats = [get_lang('SelectACategory')];
12009
        }
12010
12011
        if (!empty($items)) {
12012
            foreach ($items as $cat) {
12013
                $cats[$cat->getId()] = $cat->getName();
12014
            }
12015
        }
12016
12017
        return $cats;
12018
    }
12019
12020
    /**
12021
     * @param string $courseCode
12022
     * @param int    $lpId
12023
     * @param int    $user_id
12024
     *
12025
     * @return learnpath
12026
     */
12027
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12028
    {
12029
        $debug = 0;
12030
        $learnPath = null;
12031
        $lpObject = Session::read('lpobject');
12032
        if ($lpObject !== null) {
12033
            $learnPath = unserialize($lpObject);
12034
            if ($debug) {
12035
                error_log('getLpFromSession: unserialize');
12036
                error_log('------getLpFromSession------');
12037
                error_log('------unserialize------');
12038
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12039
                error_log("api_get_sessionid: ".api_get_session_id());
12040
            }
12041
        }
12042
12043
        if (!is_object($learnPath)) {
12044
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12045
            if ($debug) {
12046
                error_log('------getLpFromSession------');
12047
                error_log('getLpFromSession: create new learnpath');
12048
                error_log("create new LP with $courseCode - $lpId - $user_id");
12049
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12050
                error_log("api_get_sessionid: ".api_get_session_id());
12051
            }
12052
        }
12053
12054
        return $learnPath;
12055
    }
12056
12057
    /**
12058
     * @param int $itemId
12059
     *
12060
     * @return learnpathItem|false
12061
     */
12062
    public function getItem($itemId)
12063
    {
12064
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12065
            return $this->items[$itemId];
12066
        }
12067
12068
        return false;
12069
    }
12070
12071
    /**
12072
     * @return int
12073
     */
12074
    public function getCategoryId()
12075
    {
12076
        return $this->categoryId;
12077
    }
12078
12079
    /**
12080
     * @param int $categoryId
12081
     *
12082
     * @return bool
12083
     */
12084
    public function setCategoryId($categoryId)
12085
    {
12086
        $this->categoryId = intval($categoryId);
12087
        $table = Database::get_course_table(TABLE_LP_MAIN);
12088
        $lp_id = $this->get_id();
12089
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12090
                WHERE iid = $lp_id";
12091
        Database::query($sql);
12092
12093
        return true;
12094
    }
12095
12096
    /**
12097
     * Get whether this is a learning path with the possibility to subscribe
12098
     * users or not.
12099
     *
12100
     * @return int
12101
     */
12102
    public function getSubscribeUsers()
12103
    {
12104
        return $this->subscribeUsers;
12105
    }
12106
12107
    /**
12108
     * Set whether this is a learning path with the possibility to subscribe
12109
     * users or not.
12110
     *
12111
     * @param int $value (0 = false, 1 = true)
12112
     *
12113
     * @return bool
12114
     */
12115
    public function setSubscribeUsers($value)
12116
    {
12117
        if ($this->debug > 0) {
12118
            error_log('In learnpath::set_subscribe_users()', 0);
12119
        }
12120
        $this->subscribeUsers = (int) $value;
12121
        $table = Database::get_course_table(TABLE_LP_MAIN);
12122
        $lp_id = $this->get_id();
12123
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12124
                WHERE iid = $lp_id";
12125
        Database::query($sql);
12126
12127
        return true;
12128
    }
12129
12130
    /**
12131
     * Calculate the count of stars for a user in this LP
12132
     * This calculation is based on the following rules:
12133
     * - the student gets one star when he gets to 50% of the learning path
12134
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12135
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12136
     * - the student gets the final star when the score for the *last* test is >= 80%.
12137
     *
12138
     * @param int $sessionId Optional. The session ID
12139
     *
12140
     * @return int The count of stars
12141
     */
12142
    public function getCalculateStars($sessionId = 0)
12143
    {
12144
        $stars = 0;
12145
        $progress = self::getProgress(
12146
            $this->lp_id,
12147
            $this->user_id,
12148
            $this->course_int_id,
12149
            $sessionId
12150
        );
12151
12152
        if ($progress >= 50) {
12153
            $stars++;
12154
        }
12155
12156
        // Calculate stars chapters evaluation
12157
        $exercisesItems = $this->getExercisesItems();
12158
12159
        if (!empty($exercisesItems)) {
12160
            $totalResult = 0;
12161
12162
            foreach ($exercisesItems as $exerciseItem) {
12163
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12164
                    $this->user_id,
12165
                    $exerciseItem->path,
12166
                    $this->course_int_id,
12167
                    $sessionId,
12168
                    $this->lp_id,
12169
                    $exerciseItem->db_id
12170
                );
12171
12172
                $exerciseResultInfo = end($exerciseResultInfo);
12173
12174
                if (!$exerciseResultInfo) {
12175
                    continue;
12176
                }
12177
12178
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12179
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12180
                } else {
12181
                    $exerciseResult = 0;
12182
                }
12183
                $totalResult += $exerciseResult;
12184
            }
12185
12186
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12187
12188
            if ($totalExerciseAverage >= 50) {
12189
                $stars++;
12190
            }
12191
12192
            if ($totalExerciseAverage >= 80) {
12193
                $stars++;
12194
            }
12195
        }
12196
12197
        // Calculate star for final evaluation
12198
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12199
12200
        if (!empty($finalEvaluationItem)) {
12201
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12202
                $this->user_id,
12203
                $finalEvaluationItem->path,
12204
                $this->course_int_id,
12205
                $sessionId,
12206
                $this->lp_id,
12207
                $finalEvaluationItem->db_id
12208
            );
12209
12210
            $evaluationResultInfo = end($evaluationResultInfo);
12211
12212
            if ($evaluationResultInfo) {
12213
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12214
12215
                if ($evaluationResult >= 80) {
12216
                    $stars++;
12217
                }
12218
            }
12219
        }
12220
12221
        return $stars;
12222
    }
12223
12224
    /**
12225
     * Get the items of exercise type.
12226
     *
12227
     * @return array The items. Otherwise return false
12228
     */
12229
    public function getExercisesItems()
12230
    {
12231
        $exercises = [];
12232
        foreach ($this->items as $item) {
12233
            if ($item->type != 'quiz') {
12234
                continue;
12235
            }
12236
            $exercises[] = $item;
12237
        }
12238
12239
        array_pop($exercises);
12240
12241
        return $exercises;
12242
    }
12243
12244
    /**
12245
     * Get the item of exercise type (evaluation type).
12246
     *
12247
     * @return array The final evaluation. Otherwise return false
12248
     */
12249
    public function getFinalEvaluationItem()
12250
    {
12251
        $exercises = [];
12252
        foreach ($this->items as $item) {
12253
            if ($item->type != 'quiz') {
12254
                continue;
12255
            }
12256
12257
            $exercises[] = $item;
12258
        }
12259
12260
        return array_pop($exercises);
12261
    }
12262
12263
    /**
12264
     * Calculate the total points achieved for the current user in this learning path.
12265
     *
12266
     * @param int $sessionId Optional. The session Id
12267
     *
12268
     * @return int
12269
     */
12270
    public function getCalculateScore($sessionId = 0)
12271
    {
12272
        // Calculate stars chapters evaluation
12273
        $exercisesItems = $this->getExercisesItems();
12274
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12275
        $totalExercisesResult = 0;
12276
        $totalEvaluationResult = 0;
12277
12278
        if ($exercisesItems !== false) {
12279
            foreach ($exercisesItems as $exerciseItem) {
12280
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12281
                    $this->user_id,
12282
                    $exerciseItem->path,
12283
                    $this->course_int_id,
12284
                    $sessionId,
12285
                    $this->lp_id,
12286
                    $exerciseItem->db_id
12287
                );
12288
12289
                $exerciseResultInfo = end($exerciseResultInfo);
12290
12291
                if (!$exerciseResultInfo) {
12292
                    continue;
12293
                }
12294
12295
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12296
            }
12297
        }
12298
12299
        if (!empty($finalEvaluationItem)) {
12300
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12301
                $this->user_id,
12302
                $finalEvaluationItem->path,
12303
                $this->course_int_id,
12304
                $sessionId,
12305
                $this->lp_id,
12306
                $finalEvaluationItem->db_id
12307
            );
12308
12309
            $evaluationResultInfo = end($evaluationResultInfo);
12310
12311
            if ($evaluationResultInfo) {
12312
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12313
            }
12314
        }
12315
12316
        return $totalExercisesResult + $totalEvaluationResult;
12317
    }
12318
12319
    /**
12320
     * Check if URL is not allowed to be show in a iframe.
12321
     *
12322
     * @param string $src
12323
     *
12324
     * @return string
12325
     */
12326
    public function fixBlockedLinks($src)
12327
    {
12328
        $urlInfo = parse_url($src);
12329
12330
        $platformProtocol = 'https';
12331
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12332
            $platformProtocol = 'http';
12333
        }
12334
12335
        $protocolFixApplied = false;
12336
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12337
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12338
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12339
12340
        if ($platformProtocol != $scheme) {
12341
            Session::write('x_frame_source', $src);
12342
            $src = 'blank.php?error=x_frames_options';
12343
            $protocolFixApplied = true;
12344
        }
12345
12346
        if ($protocolFixApplied == false) {
12347
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12348
                // Check X-Frame-Options
12349
                $ch = curl_init();
12350
                $options = [
12351
                    CURLOPT_URL => $src,
12352
                    CURLOPT_RETURNTRANSFER => true,
12353
                    CURLOPT_HEADER => true,
12354
                    CURLOPT_FOLLOWLOCATION => true,
12355
                    CURLOPT_ENCODING => "",
12356
                    CURLOPT_AUTOREFERER => true,
12357
                    CURLOPT_CONNECTTIMEOUT => 120,
12358
                    CURLOPT_TIMEOUT => 120,
12359
                    CURLOPT_MAXREDIRS => 10,
12360
                ];
12361
12362
                $proxySettings = api_get_configuration_value('proxy_settings');
12363
                if (!empty($proxySettings) &&
12364
                    isset($proxySettings['curl_setopt_array'])
12365
                ) {
12366
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12367
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12368
                }
12369
12370
                curl_setopt_array($ch, $options);
12371
                $response = curl_exec($ch);
12372
                $httpCode = curl_getinfo($ch);
12373
                $headers = substr($response, 0, $httpCode['header_size']);
12374
12375
                $error = false;
12376
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12377
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12378
                ) {
12379
                    $error = true;
12380
                }
12381
12382
                if ($error) {
12383
                    Session::write('x_frame_source', $src);
12384
                    $src = 'blank.php?error=x_frames_options';
12385
                }
12386
            }
12387
        }
12388
12389
        return $src;
12390
    }
12391
12392
    /**
12393
     * Check if this LP has a created forum in the basis course.
12394
     *
12395
     * @return bool
12396
     */
12397
    public function lpHasForum()
12398
    {
12399
        $forumTable = Database::get_course_table(TABLE_FORUM);
12400
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12401
12402
        $fakeFrom = "
12403
            $forumTable f
12404
            INNER JOIN $itemProperty ip
12405
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12406
        ";
12407
12408
        $resultData = Database::select(
12409
            'COUNT(f.iid) AS qty',
12410
            $fakeFrom,
12411
            [
12412
                'where' => [
12413
                    'ip.visibility != ? AND ' => 2,
12414
                    'ip.tool = ? AND ' => TOOL_FORUM,
12415
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12416
                    'f.lp_id = ?' => intval($this->lp_id),
12417
                ],
12418
            ],
12419
            'first'
12420
        );
12421
12422
        return $resultData['qty'] > 0;
12423
    }
12424
12425
    /**
12426
     * Get the forum for this learning path.
12427
     *
12428
     * @param int $sessionId
12429
     *
12430
     * @return bool
12431
     */
12432
    public function getForum($sessionId = 0)
12433
    {
12434
        $forumTable = Database::get_course_table(TABLE_FORUM);
12435
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12436
12437
        $fakeFrom = "$forumTable f
12438
            INNER JOIN $itemProperty ip ";
12439
12440
        if ($this->lp_session_id == 0) {
12441
            $fakeFrom .= "
12442
                ON (
12443
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12444
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12445
                    )
12446
                )
12447
            ";
12448
        } else {
12449
            $fakeFrom .= "
12450
                ON (
12451
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12452
                )
12453
            ";
12454
        }
12455
12456
        $resultData = Database::select(
12457
            'f.*',
12458
            $fakeFrom,
12459
            [
12460
                'where' => [
12461
                    'ip.visibility != ? AND ' => 2,
12462
                    'ip.tool = ? AND ' => TOOL_FORUM,
12463
                    'f.session_id = ? AND ' => $sessionId,
12464
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12465
                    'f.lp_id = ?' => intval($this->lp_id),
12466
                ],
12467
            ],
12468
            'first'
12469
        );
12470
12471
        if (empty($resultData)) {
12472
            return false;
12473
        }
12474
12475
        return $resultData;
12476
    }
12477
12478
    /**
12479
     * Create a forum for this learning path.
12480
     *
12481
     * @param int $forumCategoryId
12482
     *
12483
     * @return int The forum ID if was created. Otherwise return false
12484
     */
12485
    public function createForum($forumCategoryId)
12486
    {
12487
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12488
12489
        $forumId = store_forum(
12490
            [
12491
                'lp_id' => $this->lp_id,
12492
                'forum_title' => $this->name,
12493
                'forum_comment' => null,
12494
                'forum_category' => intval($forumCategoryId),
12495
                'students_can_edit_group' => ['students_can_edit' => 0],
12496
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12497
                'default_view_type_group' => ['default_view_type' => 'flat'],
12498
                'group_forum' => 0,
12499
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12500
            ],
12501
            [],
12502
            true
12503
        );
12504
12505
        return $forumId;
12506
    }
12507
12508
    /**
12509
     * Get the LP Final Item form.
12510
     *
12511
     * @throws Exception
12512
     * @throws HTML_QuickForm_Error
12513
     *
12514
     * @return string
12515
     */
12516
    public function getFinalItemForm()
12517
    {
12518
        $finalItem = $this->getFinalItem();
12519
        $title = '';
12520
12521
        if ($finalItem) {
12522
            $title = $finalItem->get_title();
12523
            $buttonText = get_lang('Save');
12524
            $content = $this->getSavedFinalItem();
12525
        } else {
12526
            $buttonText = get_lang('LPCreateDocument');
12527
            $content = $this->getFinalItemTemplate();
12528
        }
12529
12530
        $courseInfo = api_get_course_info();
12531
        $result = $this->generate_lp_folder($courseInfo);
12532
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12533
        $relative_prefix = '../../';
12534
12535
        $editorConfig = [
12536
            'ToolbarSet' => 'LearningPathDocuments',
12537
            'Width' => '100%',
12538
            'Height' => '500',
12539
            'FullPage' => true,
12540
            'CreateDocumentDir' => $relative_prefix,
12541
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12542
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12543
        ];
12544
12545
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12546
            'type' => 'document',
12547
            'lp_id' => $this->lp_id,
12548
        ]);
12549
12550
        $form = new FormValidator('final_item', 'POST', $url);
12551
        $form->addText('title', get_lang('Title'));
12552
        $form->addButtonSave($buttonText);
12553
        $form->addHtml(
12554
            Display::return_message(
12555
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12556
                'normal',
12557
                false
12558
            )
12559
        );
12560
12561
        $renderer = $form->defaultRenderer();
12562
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12563
12564
        $form->addHtmlEditor(
12565
            'content_lp_certificate',
12566
            null,
12567
            true,
12568
            false,
12569
            $editorConfig,
12570
            true
12571
        );
12572
        $form->addHidden('action', 'add_final_item');
12573
        $form->addHidden('path', Session::read('pathItem'));
12574
        $form->addHidden('previous', $this->get_last());
12575
        $form->setDefaults(
12576
            ['title' => $title, 'content_lp_certificate' => $content]
12577
        );
12578
12579
        if ($form->validate()) {
12580
            $values = $form->exportValues();
12581
            $lastItemId = $this->get_last();
12582
12583
            if (!$finalItem) {
12584
                $documentId = $this->create_document(
12585
                    $this->course_info,
12586
                    $values['content_lp_certificate'],
12587
                    $values['title']
12588
                );
12589
                $this->add_item(
12590
                    0,
12591
                    $lastItemId,
12592
                    'final_item',
12593
                    $documentId,
12594
                    $values['title'],
12595
                    ''
12596
                );
12597
12598
                Display::addFlash(
12599
                    Display::return_message(get_lang('Added'))
12600
                );
12601
            } else {
12602
                $this->edit_document($this->course_info);
12603
            }
12604
        }
12605
12606
        return $form->returnForm();
12607
    }
12608
12609
    /**
12610
     * Check if the current lp item is first, both, last or none from lp list.
12611
     *
12612
     * @param int $currentItemId
12613
     *
12614
     * @return string
12615
     */
12616
    public function isFirstOrLastItem($currentItemId)
12617
    {
12618
        if ($this->debug > 0) {
12619
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
12620
        }
12621
12622
        $lpItemId = [];
12623
        $typeListNotToVerify = self::getChapterTypes();
12624
12625
        // Using get_toc() function instead $this->items because returns the correct order of the items
12626
        foreach ($this->get_toc() as $item) {
12627
            if (!in_array($item['type'], $typeListNotToVerify)) {
12628
                $lpItemId[] = $item['id'];
12629
            }
12630
        }
12631
12632
        $lastLpItemIndex = count($lpItemId) - 1;
12633
        $position = array_search($currentItemId, $lpItemId);
12634
12635
        switch ($position) {
12636
            case 0:
12637
                if (!$lastLpItemIndex) {
12638
                    $answer = 'both';
12639
                    break;
12640
                }
12641
12642
                $answer = 'first';
12643
                break;
12644
            case $lastLpItemIndex:
12645
                $answer = 'last';
12646
                break;
12647
            default:
12648
                $answer = 'none';
12649
        }
12650
12651
        return $answer;
12652
    }
12653
12654
    /**
12655
     * Get whether this is a learning path with the accumulated SCORM time or not.
12656
     *
12657
     * @return int
12658
     */
12659
    public function getAccumulateScormTime()
12660
    {
12661
        return $this->accumulateScormTime;
12662
    }
12663
12664
    /**
12665
     * Set whether this is a learning path with the accumulated SCORM time or not.
12666
     *
12667
     * @param int $value (0 = false, 1 = true)
12668
     *
12669
     * @return bool Always returns true
12670
     */
12671
    public function setAccumulateScormTime($value)
12672
    {
12673
        if ($this->debug > 0) {
12674
            error_log('In learnpath::setAccumulateScormTime()', 0);
12675
        }
12676
        $this->accumulateScormTime = intval($value);
12677
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12678
        $lp_id = $this->get_id();
12679
        $sql = "UPDATE $lp_table 
12680
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12681
                WHERE iid = $lp_id";
12682
        Database::query($sql);
12683
12684
        return true;
12685
    }
12686
12687
    /**
12688
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12689
     * the new learning path tool.
12690
     *
12691
     * The function is a big switch on tool type.
12692
     * In each case, we query the corresponding table for information and build the link
12693
     * with that information.
12694
     *
12695
     * @author Yannick Warnier <[email protected]> - rebranding based on
12696
     * previous work (display_addedresource_link_in_learnpath())
12697
     *
12698
     * @param int $course_id      Course code
12699
     * @param int $learningPathId The learning path ID (in lp table)
12700
     * @param int $id_in_path     the unique index in the items table
12701
     * @param int $lpViewId
12702
     *
12703
     * @return string
12704
     */
12705
    public static function rl_get_resource_link_for_learnpath(
12706
        $course_id,
12707
        $learningPathId,
12708
        $id_in_path,
12709
        $lpViewId
12710
    ) {
12711
        $session_id = api_get_session_id();
12712
        $course_info = api_get_course_info_by_id($course_id);
12713
12714
        $learningPathId = intval($learningPathId);
12715
        $id_in_path = intval($id_in_path);
12716
        $lpViewId = intval($lpViewId);
12717
12718
        $em = Database::getManager();
12719
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12720
        /** @var CLpItem $rowItem */
12721
        $rowItem = $lpItemRepo->findOneBy([
12722
            'cId' => $course_id,
12723
            'lpId' => $learningPathId,
12724
            'id' => $id_in_path,
12725
        ]);
12726
12727
        if (!$rowItem) {
12728
            // Try one more time with iid
12729
            /** @var CLpItem $rowItem */
12730
            $rowItem = $lpItemRepo->findOneBy([
12731
                'cId' => $course_id,
12732
                'lpId' => $learningPathId,
12733
                'iid' => $id_in_path,
12734
            ]);
12735
12736
            if (!$rowItem) {
12737
                return -1;
12738
            }
12739
        }
12740
12741
        $course_code = $course_info['code'];
12742
        $type = $rowItem->getItemType();
12743
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12744
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12745
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12746
        $link = '';
12747
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12748
12749
        switch ($type) {
12750
            case 'dir':
12751
                return $main_dir_path.'lp/blank.php';
12752
            case TOOL_CALENDAR_EVENT:
12753
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12754
            case TOOL_ANNOUNCEMENT:
12755
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12756
            case TOOL_LINK:
12757
                $linkInfo = Link::getLinkInfo($id);
12758
                if (isset($linkInfo['url'])) {
12759
                    return $linkInfo['url'];
12760
                }
12761
12762
                return '';
12763
            case TOOL_QUIZ:
12764
                if (empty($id)) {
12765
                    return '';
12766
                }
12767
12768
                // Get the lp_item_view with the highest view_count.
12769
                $learnpathItemViewResult = $em
12770
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12771
                    ->findBy(
12772
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12773
                        ['viewCount' => 'DESC'],
12774
                        1
12775
                    );
12776
                /** @var CLpItemView $learnpathItemViewData */
12777
                $learnpathItemViewData = current($learnpathItemViewResult);
12778
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12779
12780
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12781
                    .http_build_query([
12782
                        'lp_init' => 1,
12783
                        'learnpath_item_view_id' => $learnpathItemViewId,
12784
                        'learnpath_id' => $learningPathId,
12785
                        'learnpath_item_id' => $id_in_path,
12786
                        'exerciseId' => $id,
12787
                    ]);
12788
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12789
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12790
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12791
                $myrow = Database::fetch_array($result);
12792
                $path = $myrow['path'];
12793
12794
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12795
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12796
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12797
            case TOOL_FORUM:
12798
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12799
            case TOOL_THREAD:
12800
                // forum post
12801
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12802
                if (empty($id)) {
12803
                    return '';
12804
                }
12805
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12806
                $result = Database::query($sql);
12807
                $myrow = Database::fetch_array($result);
12808
12809
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12810
                    .$extraParams;
12811
            case TOOL_POST:
12812
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12813
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12814
                $myrow = Database::fetch_array($result);
12815
12816
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12817
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12818
            case TOOL_DOCUMENT:
12819
                $document = $em
12820
                    ->getRepository('ChamiloCourseBundle:CDocument')
12821
                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
12822
12823
                if (empty($document)) {
12824
                    // Try with normal id
12825
                    $document = $em
12826
                        ->getRepository('ChamiloCourseBundle:CDocument')
12827
                        ->findOneBy(['cId' => $course_id, 'id' => $id]);
12828
12829
                    if (empty($document)) {
12830
                        return '';
12831
                    }
12832
                }
12833
12834
                $documentPathInfo = pathinfo($document->getPath());
12835
                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
12836
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12837
                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
12838
12839
                $openmethod = 2;
12840
                $officedoc = false;
12841
                Session::write('openmethod', $openmethod);
12842
                Session::write('officedoc', $officedoc);
12843
12844
                if ($showDirectUrl) {
12845
                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12846
                }
12847
12848
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12849
            case TOOL_LP_FINAL_ITEM:
12850
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12851
                    .$extraParams;
12852
            case 'assignments':
12853
                return $main_dir_path.'work/work.php?'.$extraParams;
12854
            case TOOL_DROPBOX:
12855
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12856
            case 'introduction_text': //DEPRECATED
12857
                return '';
12858
            case TOOL_COURSE_DESCRIPTION:
12859
                return $main_dir_path.'course_description?'.$extraParams;
12860
            case TOOL_GROUP:
12861
                return $main_dir_path.'group/group.php?'.$extraParams;
12862
            case TOOL_USER:
12863
                return $main_dir_path.'user/user.php?'.$extraParams;
12864
            case TOOL_STUDENTPUBLICATION:
12865
                if (!empty($rowItem->getPath())) {
12866
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12867
                }
12868
12869
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12870
        } //end switch
12871
12872
        return $link;
12873
    }
12874
12875
    /**
12876
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12877
     *
12878
     * @author Yannick Warnier <[email protected]>
12879
     *
12880
     * @param string $course_code    Course code
12881
     * @param string $learningPathId The tool type (using constants declared in main_api.lib.php)
12882
     * @param int    $id_in_path     The resource ID
12883
     *
12884
     * @return string
12885
     */
12886
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12887
    {
12888
        $_course = api_get_course_info($course_code);
12889
        $course_id = $_course['real_id'];
12890
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12891
        $learningPathId = intval($learningPathId);
12892
        $id_in_path = intval($id_in_path);
12893
12894
        $sql_item = "SELECT item_type, title, ref FROM $tbl_lp_item
12895
                     WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12896
        $res_item = Database::query($sql_item);
12897
12898
        if (Database::num_rows($res_item) < 1) {
12899
            return '';
12900
        }
12901
        $row_item = Database::fetch_array($res_item);
12902
        $type = strtolower($row_item['item_type']);
12903
        $id = $row_item['ref'];
12904
        $output = '';
12905
12906
        switch ($type) {
12907
            case TOOL_CALENDAR_EVENT:
12908
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12909
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12910
                $myrow = Database::fetch_array($result);
12911
                $output = $myrow['title'];
12912
                break;
12913
            case TOOL_ANNOUNCEMENT:
12914
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12915
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12916
                $myrow = Database::fetch_array($result);
12917
                $output = $myrow['title'];
12918
                break;
12919
            case TOOL_LINK:
12920
                // Doesn't take $target into account.
12921
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12922
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12923
                $myrow = Database::fetch_array($result);
12924
                $output = $myrow['title'];
12925
                break;
12926
            case TOOL_QUIZ:
12927
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12928
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id=$id");
12929
                $myrow = Database::fetch_array($result);
12930
                $output = $myrow['title'];
12931
                break;
12932
            case TOOL_FORUM:
12933
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12934
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id=$id");
12935
                $myrow = Database::fetch_array($result);
12936
                $output = $myrow['forum_name'];
12937
                break;
12938
            case TOOL_THREAD:  //=topics
12939
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12940
                // Grabbing the title of the post.
12941
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12942
                $result_title = Database::query($sql_title);
12943
                $myrow_title = Database::fetch_array($result_title);
12944
                $output = $myrow_title['post_title'];
12945
                break;
12946
            case TOOL_POST:
12947
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12948
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
12949
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12950
                $result = Database::query($sql);
12951
                $post = Database::fetch_array($result);
12952
                $output = $post['post_title'];
12953
                break;
12954
            case 'dir':
12955
                $title = $row_item['title'];
12956
                if (!empty($title)) {
12957
                    $output = $title;
12958
                } else {
12959
                    $output = '-';
12960
                }
12961
                break;
12962
            case TOOL_DOCUMENT:
12963
                $title = $row_item['title'];
12964
                if (!empty($title)) {
12965
                    $output = $title;
12966
                } else {
12967
                    $output = '-';
12968
                }
12969
                break;
12970
            case 'hotpotatoes':
12971
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12972
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12973
                $myrow = Database::fetch_array($result);
12974
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12975
                $last = count($pathname) - 1; // Making a correct name for the link.
12976
                $filename = $pathname[$last]; // Making a correct name for the link.
12977
                $ext = explode('.', $filename);
12978
                $ext = strtolower($ext[sizeof($ext) - 1]);
12979
                $myrow['path'] = rawurlencode($myrow['path']);
12980
                $output = $filename;
12981
                break;
12982
        }
12983
12984
        return stripslashes($output);
12985
    }
12986
12987
    /**
12988
     * Get the parent names for the current item.
12989
     *
12990
     * @param int $newItemId Optional. The item ID
12991
     *
12992
     * @return array
12993
     */
12994
    public function getCurrentItemParentNames($newItemId = 0)
12995
    {
12996
        $newItemId = $newItemId ?: $this->get_current_item_id();
12997
        $return = [];
12998
        $item = $this->getItem($newItemId);
12999
        $parent = $this->getItem($item->get_parent());
13000
13001
        while ($parent) {
13002
            $return[] = $parent->get_title();
13003
13004
            $parent = $this->getItem($parent->get_parent());
13005
        }
13006
13007
        return array_reverse($return);
13008
    }
13009
13010
    /**
13011
     * Reads and process "lp_subscription_settings" setting.
13012
     *
13013
     * @return array
13014
     */
13015
    public static function getSubscriptionSettings()
13016
    {
13017
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13018
        if (empty($subscriptionSettings)) {
13019
            // By default allow both settings
13020
            $subscriptionSettings = [
13021
                'allow_add_users_to_lp' => true,
13022
                'allow_add_users_to_lp_category' => true,
13023
            ];
13024
        } else {
13025
            $subscriptionSettings = $subscriptionSettings['options'];
13026
        }
13027
13028
        return $subscriptionSettings;
13029
    }
13030
13031
    /**
13032
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13033
     */
13034
    public function exportToCourseBuildFormat()
13035
    {
13036
        if (!api_is_allowed_to_edit()) {
13037
            return false;
13038
        }
13039
13040
        $courseBuilder = new CourseBuilder();
13041
        $itemList = [];
13042
        /** @var learnpathItem $item */
13043
        foreach ($this->items as $item) {
13044
            $itemList[$item->get_type()][] = $item->get_path();
13045
        }
13046
13047
        if (empty($itemList)) {
13048
            return false;
13049
        }
13050
13051
        if (isset($itemList['document'])) {
13052
            // Get parents
13053
            foreach ($itemList['document'] as $documentId) {
13054
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13055
                if (!empty($documentInfo['parents'])) {
13056
                    foreach ($documentInfo['parents'] as $parentInfo) {
13057
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13058
                            continue;
13059
                        }
13060
                        $itemList['document'][] = $parentInfo['iid'];
13061
                    }
13062
                }
13063
            }
13064
13065
            $courseInfo = api_get_course_info();
13066
            foreach ($itemList['document'] as $documentId) {
13067
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13068
                $items = DocumentManager::get_resources_from_source_html(
13069
                    $documentInfo['absolute_path'],
13070
                    true,
13071
                    TOOL_DOCUMENT
13072
                );
13073
13074
                if (!empty($items)) {
13075
                    foreach ($items as $item) {
13076
                        // Get information about source url
13077
                        $url = $item[0]; // url
13078
                        $scope = $item[1]; // scope (local, remote)
13079
                        $type = $item[2]; // type (rel, abs, url)
13080
13081
                        $origParseUrl = parse_url($url);
13082
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13083
13084
                        if ($scope == 'local') {
13085
                            if ($type == 'abs' || $type == 'rel') {
13086
                                $documentFile = strstr($realOrigPath, 'document');
13087
                                if (strpos($realOrigPath, $documentFile) !== false) {
13088
                                    $documentFile = str_replace('document', '', $documentFile);
13089
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13090
                                    // Document found! Add it to the list
13091
                                    if ($itemDocumentId) {
13092
                                        $itemList['document'][] = $itemDocumentId;
13093
                                    }
13094
                                }
13095
                            }
13096
                        }
13097
                    }
13098
                }
13099
            }
13100
13101
            $courseBuilder->build_documents(
13102
                api_get_session_id(),
13103
                $this->get_course_int_id(),
13104
                true,
13105
                $itemList['document']
13106
            );
13107
        }
13108
13109
        if (isset($itemList['quiz'])) {
13110
            $courseBuilder->build_quizzes(
13111
                api_get_session_id(),
13112
                $this->get_course_int_id(),
13113
                true,
13114
                $itemList['quiz']
13115
            );
13116
        }
13117
13118
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13119
13120
        /*if (!empty($itemList['thread'])) {
13121
            $postList = [];
13122
            foreach ($itemList['thread'] as $postId) {
13123
                $post = get_post_information($postId);
13124
                if ($post) {
13125
                    if (!isset($itemList['forum'])) {
13126
                        $itemList['forum'] = [];
13127
                    }
13128
                    $itemList['forum'][] = $post['forum_id'];
13129
                    $postList[] = $postId;
13130
                }
13131
            }
13132
13133
            if (!empty($postList)) {
13134
                $courseBuilder->build_forum_posts(
13135
                    $this->get_course_int_id(),
13136
                    null,
13137
                    null,
13138
                    $postList
13139
                );
13140
            }
13141
        }*/
13142
13143
        if (!empty($itemList['thread'])) {
13144
            $threadList = [];
13145
            $em = Database::getManager();
13146
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13147
            foreach ($itemList['thread'] as $threadId) {
13148
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13149
                $thread = $repo->find($threadId);
13150
                if ($thread) {
13151
                    $itemList['forum'][] = $thread->getForumId();
13152
                    $threadList[] = $thread->getIid();
13153
                }
13154
            }
13155
13156
            if (!empty($threadList)) {
13157
                $courseBuilder->build_forum_topics(
13158
                    api_get_session_id(),
13159
                    $this->get_course_int_id(),
13160
                    null,
13161
                    $threadList
13162
                );
13163
            }
13164
        }
13165
13166
        $forumCategoryList = [];
13167
        if (isset($itemList['forum'])) {
13168
            foreach ($itemList['forum'] as $forumId) {
13169
                $forumInfo = get_forums($forumId);
13170
                $forumCategoryList[] = $forumInfo['forum_category'];
13171
            }
13172
        }
13173
13174
        if (!empty($forumCategoryList)) {
13175
            $courseBuilder->build_forum_category(
13176
                api_get_session_id(),
13177
                $this->get_course_int_id(),
13178
                true,
13179
                $forumCategoryList
13180
            );
13181
        }
13182
13183
        if (!empty($itemList['forum'])) {
13184
            $courseBuilder->build_forums(
13185
                api_get_session_id(),
13186
                $this->get_course_int_id(),
13187
                true,
13188
                $itemList['forum']
13189
            );
13190
        }
13191
13192
        if (isset($itemList['link'])) {
13193
            $courseBuilder->build_links(
13194
                api_get_session_id(),
13195
                $this->get_course_int_id(),
13196
                true,
13197
                $itemList['link']
13198
            );
13199
        }
13200
13201
        if (!empty($itemList['student_publication'])) {
13202
            $courseBuilder->build_works(
13203
                api_get_session_id(),
13204
                $this->get_course_int_id(),
13205
                true,
13206
                $itemList['student_publication']
13207
            );
13208
        }
13209
13210
        $courseBuilder->build_learnpaths(
13211
            api_get_session_id(),
13212
            $this->get_course_int_id(),
13213
            true,
13214
            [$this->get_id()],
13215
            false
13216
        );
13217
13218
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13219
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13220
        $result = DocumentManager::file_send_for_download(
13221
            $zipPath,
13222
            true,
13223
            $this->get_name().'.zip'
13224
        );
13225
13226
        if ($result) {
13227
            api_not_allowed();
13228
        }
13229
13230
        return true;
13231
    }
13232
13233
    /**
13234
     * Get the depth level of LP item.
13235
     *
13236
     * @param array $items
13237
     * @param int   $currentItemId
13238
     *
13239
     * @return int
13240
     */
13241
    private static function get_level_for_item($items, $currentItemId)
13242
    {
13243
        $parentItemId = 0;
13244
        if (isset($items[$currentItemId])) {
13245
            $parentItemId = $items[$currentItemId]->parent;
13246
        }
13247
13248
        if ($parentItemId == 0) {
13249
            return 0;
13250
        } else {
13251
            return self::get_level_for_item($items, $parentItemId) + 1;
13252
        }
13253
    }
13254
13255
    /**
13256
     * Generate the link for a learnpath category as course tool.
13257
     *
13258
     * @param int $categoryId
13259
     *
13260
     * @return string
13261
     */
13262
    private static function getCategoryLinkForTool($categoryId)
13263
    {
13264
        $categoryId = (int) $categoryId;
13265
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13266
            .http_build_query(
13267
                [
13268
                    'action' => 'view_category',
13269
                    'id' => $categoryId,
13270
                ]
13271
            );
13272
13273
        return $link;
13274
    }
13275
13276
    /**
13277
     * Return the scorm item type object with spaces replaced with _
13278
     * The return result is use to build a css classname like scorm_type_$return.
13279
     *
13280
     * @param $in_type
13281
     *
13282
     * @return mixed
13283
     */
13284
    private static function format_scorm_type_item($in_type)
13285
    {
13286
        return str_replace(' ', '_', $in_type);
13287
    }
13288
13289
    /**
13290
     * Check and obtain the lp final item if exist.
13291
     *
13292
     * @return learnpathItem
13293
     */
13294
    private function getFinalItem()
13295
    {
13296
        if (empty($this->items)) {
13297
            return null;
13298
        }
13299
13300
        foreach ($this->items as $item) {
13301
            if ($item->type !== 'final_item') {
13302
                continue;
13303
            }
13304
13305
            return $item;
13306
        }
13307
    }
13308
13309
    /**
13310
     * Get the LP Final Item Template.
13311
     *
13312
     * @return string
13313
     */
13314
    private function getFinalItemTemplate()
13315
    {
13316
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13317
    }
13318
13319
    /**
13320
     * Get the LP Final Item Url.
13321
     *
13322
     * @return string
13323
     */
13324
    private function getSavedFinalItem()
13325
    {
13326
        $finalItem = $this->getFinalItem();
13327
        $doc = DocumentManager::get_document_data_by_id(
13328
            $finalItem->path,
13329
            $this->cc
13330
        );
13331
        if ($doc && file_exists($doc['absolute_path'])) {
13332
            return file_get_contents($doc['absolute_path']);
13333
        }
13334
13335
        return '';
13336
    }
13337
}
13338