Passed
Push — master ( 9d7978...5faf0b )
by Julito
10:24
created

learnpath   F

Complexity

Total Complexity 1916

Size/Duplication

Total Lines 13836
Duplicated Lines 0 %

Importance

Changes 5
Bugs 2 Features 0
Metric Value
eloc 7781
dl 0
loc 13836
rs 0.8
c 5
b 2
f 0
wmc 1916

206 Methods

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

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\Repository\CourseRepository;
5
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
8
use Chamilo\CourseBundle\Entity\CDocument;
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 const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
37
    public $attempt = 0; // The number for the current ID view.
38
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
39
    public $current; // Id of the current item the user is viewing.
40
    public $current_score; // The score of the current item.
41
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
42
    public $current_time_stop; // The time the user closed this resource.
43
    public $default_status = 'not attempted';
44
    public $encoding = 'UTF-8';
45
    public $error = '';
46
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
47
    public $index; // The index of the active learnpath_item in $ordered_items array.
48
    public $items = [];
49
    public $last; // item_id of last item viewed in the learning path.
50
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
51
    public $license; // Which license this course has been given - not used yet on 20060522.
52
    public $lp_id; // DB iid for this learnpath.
53
    public $lp_view_id; // DB ID for lp_view
54
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
55
    public $message = '';
56
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
57
    public $name; // Learnpath name (they generally have one).
58
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
59
    public $path = ''; // Path inside the scorm directory (if scorm).
60
    public $theme; // The current theme of the learning path.
61
    public $preview_image; // The current image of the learning path.
62
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
63
64
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
65
    public $prevent_reinit = 1;
66
67
    // Describes the mode of progress bar display.
68
    public $seriousgame_mode = 0;
69
    public $progress_bar_mode = '%';
70
71
    // Percentage progress as saved in the db.
72
    public $progress_db = 0;
73
    public $proximity; // Wether the content is distant or local or unknown.
74
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
75
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
76
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
77
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
78
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
79
    public $user_id; //ID of the user that is viewing/using the course
80
    public $update_queue = [];
81
    public $scorm_debug = 0;
82
    public $arrMenu = []; // Array for the menu items.
83
    public $debug = 0; // Logging level.
84
    public $lp_session_id = 0;
85
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
86
    public $prerequisite = 0;
87
    public $use_max_score = 1; // 1 or 0
88
    public $subscribeUsers = 0; // Subscribe users or not
89
    public $created_on = '';
90
    public $modified_on = '';
91
    public $publicated_on = '';
92
    public $expired_on = '';
93
    public $ref = null;
94
    public $course_int_id;
95
    public $course_info = [];
96
    public $categoryId;
97
98
    /**
99
     * Constructor.
100
     * Needs a database handler, a course code and a learnpath id from the database.
101
     * Also builds the list of items into $this->items.
102
     *
103
     * @param string $course  Course code
104
     * @param int    $lp_id   c_lp.iid
105
     * @param int    $user_id
106
     */
107
    public function __construct($course, $lp_id, $user_id)
108
    {
109
        $debug = $this->debug;
110
        $this->encoding = api_get_system_encoding();
111
        if ($debug) {
112
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
113
        }
114
        if (empty($course)) {
115
            $course = api_get_course_id();
116
        }
117
        $course_info = api_get_course_info($course);
118
        if (!empty($course_info)) {
119
            $this->cc = $course_info['code'];
120
            $this->course_info = $course_info;
121
            $course_id = $course_info['real_id'];
122
        } else {
123
            $this->error = 'Course code does not exist in database.';
124
        }
125
126
        $lp_id = (int) $lp_id;
127
        $course_id = (int) $course_id;
128
        $this->set_course_int_id($course_id);
129
        // Check learnpath ID.
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            // TODO: Make it flexible to use any course_code (still using env course code here).
134
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
135
            $sql = "SELECT * FROM $lp_table
136
                    WHERE iid = $lp_id";
137
            if ($debug) {
138
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
139
            }
140
            $res = Database::query($sql);
141
            if (Database::num_rows($res) > 0) {
142
                $this->lp_id = $lp_id;
143
                $row = Database::fetch_array($res);
144
                $this->type = $row['lp_type'];
145
                $this->name = stripslashes($row['name']);
146
                $this->proximity = $row['content_local'];
147
                $this->theme = $row['theme'];
148
                $this->maker = $row['content_maker'];
149
                $this->prevent_reinit = $row['prevent_reinit'];
150
                $this->seriousgame_mode = $row['seriousgame_mode'];
151
                $this->license = $row['content_license'];
152
                $this->scorm_debug = $row['debug'];
153
                $this->js_lib = $row['js_lib'];
154
                $this->path = $row['path'];
155
                $this->preview_image = $row['preview_image'];
156
                $this->author = $row['author'];
157
                $this->hide_toc_frame = $row['hide_toc_frame'];
158
                $this->lp_session_id = $row['session_id'];
159
                $this->use_max_score = $row['use_max_score'];
160
                $this->subscribeUsers = $row['subscribe_users'];
161
                $this->created_on = $row['created_on'];
162
                $this->modified_on = $row['modified_on'];
163
                $this->ref = $row['ref'];
164
                $this->categoryId = $row['category_id'];
165
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
166
167
                if (!empty($row['publicated_on'])) {
168
                    $this->publicated_on = $row['publicated_on'];
169
                }
170
171
                if (!empty($row['expired_on'])) {
172
                    $this->expired_on = $row['expired_on'];
173
                }
174
                if ($this->type == 2) {
175
                    if ($row['force_commit'] == 1) {
176
                        $this->force_commit = true;
177
                    }
178
                }
179
                $this->mode = $row['default_view_mod'];
180
181
                // Check user ID.
182
                if (empty($user_id)) {
183
                    $this->error = 'User ID is empty';
184
                } else {
185
                    $userInfo = api_get_user_info($user_id);
186
                    if (!empty($userInfo)) {
187
                        $this->user_id = $userInfo['user_id'];
188
                    } else {
189
                        $this->error = 'User ID does not exist in database #'.$user_id;
190
                    }
191
                }
192
193
                // End of variables checking.
194
                $session_id = api_get_session_id();
195
                //  Get the session condition for learning paths of the base + session.
196
                $session = api_get_session_condition($session_id);
197
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
198
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
199
200
                // Selecting by view_count descending allows to get the highest view_count first.
201
                $sql = "SELECT * FROM $lp_table
202
                        WHERE 
203
                            c_id = $course_id AND 
204
                            lp_id = $lp_id AND 
205
                            user_id = $user_id 
206
                            $session
207
                        ORDER BY view_count DESC";
208
                $res = Database::query($sql);
209
                if ($debug) {
210
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
211
                }
212
213
                if (Database::num_rows($res) > 0) {
214
                    if ($debug) {
215
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
216
                    }
217
                    $row = Database::fetch_array($res);
218
                    $this->attempt = $row['view_count'];
219
                    $this->lp_view_id = $row['id'];
220
                    $this->last_item_seen = $row['last_item'];
221
                    $this->progress_db = $row['progress'];
222
                    $this->lp_view_session_id = $row['session_id'];
223
                } elseif (!api_is_invitee()) {
224
                    if ($debug) {
225
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
226
                    }
227
                    $this->attempt = 1;
228
                    $params = [
229
                        'c_id' => $course_id,
230
                        'lp_id' => $lp_id,
231
                        'user_id' => $user_id,
232
                        'view_count' => 1,
233
                        'session_id' => $session_id,
234
                        'last_item' => 0,
235
                    ];
236
                    $this->last_item_seen = 0;
237
                    $this->lp_view_session_id = $session_id;
238
                    $this->lp_view_id = Database::insert($lp_table, $params);
239
                    if (!empty($this->lp_view_id)) {
240
                        $sql = "UPDATE $lp_table SET id = iid
241
                                WHERE iid = ".$this->lp_view_id;
242
                        Database::query($sql);
243
                    }
244
                }
245
246
                // Initialise items.
247
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
248
                $sql = "SELECT * FROM $lp_item_table
249
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
250
                        ORDER BY parent_item_id, display_order";
251
                $res = Database::query($sql);
252
253
                if ($debug) {
254
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
255
                    error_log('-- Start while--');
256
                }
257
258
                $lp_item_id_list = [];
259
                while ($row = Database::fetch_array($res)) {
260
                    $lp_item_id_list[] = $row['iid'];
261
                    switch ($this->type) {
262
                        case 3: //aicc
263
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
264
                            if (is_object($oItem)) {
265
                                $my_item_id = $oItem->get_id();
266
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
267
                                $oItem->set_prevent_reinit($this->prevent_reinit);
268
                                // Don't use reference here as the next loop will make the pointed object change.
269
                                $this->items[$my_item_id] = $oItem;
270
                                $this->refs_list[$oItem->ref] = $my_item_id;
271
                                if ($debug) {
272
                                    error_log(
273
                                        'learnpath::__construct() - '.
274
                                        'aicc object with id '.$my_item_id.
275
                                        ' set in items[]',
276
                                        0
277
                                    );
278
                                }
279
                            }
280
                            break;
281
                        case 2:
282
                            $oItem = new scormItem('db', $row['iid'], $course_id);
283
                            if (is_object($oItem)) {
284
                                $my_item_id = $oItem->get_id();
285
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
286
                                $oItem->set_prevent_reinit($this->prevent_reinit);
287
                                // Don't use reference here as the next loop will make the pointed object change.
288
                                $this->items[$my_item_id] = $oItem;
289
                                $this->refs_list[$oItem->ref] = $my_item_id;
290
                                if ($debug) {
291
                                    error_log('object with id '.$my_item_id.' set in items[]');
292
                                }
293
                            }
294
                            break;
295
                        case 1:
296
                        default:
297
                            if ($debug) {
298
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
299
                            }
300
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
301
302
                            if ($debug) {
303
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
304
                            }
305
                            if (is_object($oItem)) {
306
                                $my_item_id = $oItem->get_id();
307
                                // Moved down to when we are sure the item_view exists.
308
                                //$oItem->set_lp_view($this->lp_view_id);
309
                                $oItem->set_prevent_reinit($this->prevent_reinit);
310
                                // Don't use reference here as the next loop will make the pointed object change.
311
                                $this->items[$my_item_id] = $oItem;
312
                                $this->refs_list[$my_item_id] = $my_item_id;
313
                                if ($debug) {
314
                                    error_log(
315
                                        'learnpath::__construct() '.__LINE__.
316
                                        ' - object with id '.$my_item_id.' set in items[]'
317
                                    );
318
                                }
319
                            }
320
                            break;
321
                    }
322
323
                    // Setting the object level with variable $this->items[$i][parent]
324
                    foreach ($this->items as $itemLPObject) {
325
                        $level = self::get_level_for_item(
326
                            $this->items,
327
                            $itemLPObject->db_id
328
                        );
329
                        $itemLPObject->level = $level;
330
                    }
331
332
                    // Setting the view in the item object.
333
                    if (is_object($this->items[$row['iid']])) {
334
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
335
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
336
                            $this->items[$row['iid']]->current_start_time = 0;
337
                            $this->items[$row['iid']]->current_stop_time = 0;
338
                        }
339
                    }
340
                }
341
342
                if ($debug) {
343
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
344
                }
345
346
                if (!empty($lp_item_id_list)) {
347
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
348
                    if (!empty($lp_item_id_list_to_string)) {
349
                        // Get last viewing vars.
350
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
351
                        // This query should only return one or zero result.
352
                        $sql = "SELECT lp_item_id, status
353
                                FROM $itemViewTable
354
                                WHERE
355
                                    c_id = $course_id AND
356
                                    lp_view_id = ".$this->lp_view_id." AND
357
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
358
                                ORDER BY view_count DESC ";
359
360
                        if ($debug) {
361
                            error_log(
362
                                'learnpath::__construct() - Selecting item_views: '.$sql,
363
                                0
364
                            );
365
                        }
366
367
                        $status_list = [];
368
                        $res = Database::query($sql);
369
                        while ($row = Database:: fetch_array($res)) {
370
                            $status_list[$row['lp_item_id']] = $row['status'];
371
                        }
372
373
                        foreach ($lp_item_id_list as $item_id) {
374
                            if (isset($status_list[$item_id])) {
375
                                $status = $status_list[$item_id];
376
                                if (is_object($this->items[$item_id])) {
377
                                    $this->items[$item_id]->set_status($status);
378
                                    if (empty($status)) {
379
                                        $this->items[$item_id]->set_status(
380
                                            $this->default_status
381
                                        );
382
                                    }
383
                                }
384
                            } else {
385
                                if (!api_is_invitee()) {
386
                                    if (is_object($this->items[$item_id])) {
387
                                        $this->items[$item_id]->set_status(
388
                                            $this->default_status
389
                                        );
390
                                    }
391
392
                                    if (!empty($this->lp_view_id)) {
393
                                        // Add that row to the lp_item_view table so that
394
                                        // we have something to show in the stats page.
395
                                        $params = [
396
                                            'c_id' => $course_id,
397
                                            'lp_item_id' => $item_id,
398
                                            'lp_view_id' => $this->lp_view_id,
399
                                            'view_count' => 1,
400
                                            'status' => 'not attempted',
401
                                            'start_time' => time(),
402
                                            'total_time' => 0,
403
                                            'score' => 0,
404
                                        ];
405
                                        $insertId = Database::insert($itemViewTable, $params);
406
407
                                        if ($insertId) {
408
                                            $sql = "UPDATE $itemViewTable SET id = iid
409
                                                    WHERE iid = $insertId";
410
                                            Database::query($sql);
411
                                        }
412
413
                                        $this->items[$item_id]->set_lp_view(
414
                                            $this->lp_view_id,
415
                                            $course_id
416
                                        );
417
                                    }
418
                                }
419
                            }
420
                        }
421
                    }
422
                }
423
424
                $this->ordered_items = self::get_flat_ordered_items_list(
425
                    $this->get_id(),
426
                    0,
427
                    $course_id
428
                );
429
                $this->max_ordered_items = 0;
430
                foreach ($this->ordered_items as $index => $dummy) {
431
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
432
                        $this->max_ordered_items = $index;
433
                    }
434
                }
435
                // TODO: Define the current item better.
436
                $this->first();
437
                if ($debug) {
438
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
439
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
440
                }
441
            } else {
442
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
443
            }
444
        }
445
    }
446
447
    /**
448
     * @return string
449
     */
450
    public function getCourseCode()
451
    {
452
        return $this->cc;
453
    }
454
455
    /**
456
     * @return int
457
     */
458
    public function get_course_int_id()
459
    {
460
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
461
    }
462
463
    /**
464
     * @param $course_id
465
     *
466
     * @return int
467
     */
468
    public function set_course_int_id($course_id)
469
    {
470
        return $this->course_int_id = (int) $course_id;
471
    }
472
473
    /**
474
     * Function rewritten based on old_add_item() from Yannick Warnier.
475
     * Due the fact that users can decide where the item should come, I had to overlook this function and
476
     * I found it better to rewrite it. Old function is still available.
477
     * Added also the possibility to add a description.
478
     *
479
     * @param int    $parent
480
     * @param int    $previous
481
     * @param string $type
482
     * @param int    $id               resource ID (ref)
483
     * @param string $title
484
     * @param string $description
485
     * @param int    $prerequisites
486
     * @param int    $max_time_allowed
487
     * @param int    $userId
488
     *
489
     * @return int
490
     */
491
    public function add_item(
492
        $parent,
493
        $previous,
494
        $type = 'dir',
495
        $id,
496
        $title,
497
        $description,
498
        $prerequisites = 0,
499
        $max_time_allowed = 0,
500
        $userId = 0
501
    ) {
502
        $course_id = $this->course_info['real_id'];
503
        if ($this->debug > 0) {
504
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
505
        }
506
        if (empty($course_id)) {
507
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
508
            $this->course_info = api_get_course_info($this->cc);
509
            $course_id = $this->course_info['real_id'];
510
        }
511
        $userId = empty($userId) ? api_get_user_id() : $userId;
512
        $sessionId = api_get_session_id();
513
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
514
        $_course = $this->course_info;
515
        $parent = (int) $parent;
516
        $previous = (int) $previous;
517
        $id = (int) $id;
518
        $max_time_allowed = htmlentities($max_time_allowed);
519
        if (empty($max_time_allowed)) {
520
            $max_time_allowed = 0;
521
        }
522
        $sql = "SELECT COUNT(iid) AS num
523
                FROM $tbl_lp_item
524
                WHERE
525
                    c_id = $course_id AND
526
                    lp_id = ".$this->get_id()." AND
527
                    parent_item_id = ".$parent;
528
529
        $res_count = Database::query($sql);
530
        $row = Database::fetch_array($res_count);
531
        $num = $row['num'];
532
533
        $tmp_previous = 0;
534
        $display_order = 0;
535
        $next = 0;
536
        if ($num > 0) {
537
            if (empty($previous)) {
538
                $sql = "SELECT iid, next_item_id, display_order
539
                        FROM $tbl_lp_item
540
                        WHERE
541
                            c_id = $course_id AND
542
                            lp_id = ".$this->get_id()." AND
543
                            parent_item_id = $parent AND
544
                            previous_item_id = 0 OR
545
                            previous_item_id = $parent";
546
                $result = Database::query($sql);
547
                $row = Database::fetch_array($result);
548
                if ($row) {
549
                    $next = $row['iid'];
550
                }
551
            } else {
552
                $previous = (int) $previous;
553
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
554
						FROM $tbl_lp_item
555
                        WHERE
556
                            c_id = $course_id AND
557
                            lp_id = ".$this->get_id()." AND
558
                            id = $previous";
559
                $result = Database::query($sql);
560
                $row = Database::fetch_array($result);
561
                if ($row) {
562
                    $tmp_previous = $row['iid'];
563
                    $next = $row['next_item_id'];
564
                    $display_order = $row['display_order'];
565
                }
566
            }
567
        }
568
569
        $id = (int) $id;
570
        $typeCleaned = Database::escape_string($type);
571
        $max_score = 100;
572
        if ($type === 'quiz') {
573
            $sql = 'SELECT SUM(ponderation)
574
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
575
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
576
                    ON
577
                        quiz_question.id = quiz_rel_question.question_id AND
578
                        quiz_question.c_id = quiz_rel_question.c_id
579
                    WHERE
580
                        quiz_rel_question.exercice_id = '.$id." AND
581
                        quiz_question.c_id = $course_id AND
582
                        quiz_rel_question.c_id = $course_id ";
583
            $rsQuiz = Database::query($sql);
584
            $max_score = Database::result($rsQuiz, 0, 0);
585
586
            // Disabling the exercise if we add it inside a LP
587
            $exercise = new Exercise($course_id);
588
            $exercise->read($id);
589
            $exercise->disable();
590
            $exercise->save();
591
        }
592
593
        $params = [
594
            'c_id' => $course_id,
595
            'lp_id' => $this->get_id(),
596
            'item_type' => $typeCleaned,
597
            'ref' => '',
598
            'title' => $title,
599
            'description' => $description,
600
            'path' => $id,
601
            'max_score' => $max_score,
602
            'parent_item_id' => $parent,
603
            'previous_item_id' => $previous,
604
            'next_item_id' => (int) $next,
605
            'display_order' => $display_order + 1,
606
            'prerequisite' => $prerequisites,
607
            'max_time_allowed' => $max_time_allowed,
608
            'min_score' => 0,
609
            'launch_data' => '',
610
        ];
611
612
        if ($prerequisites != 0) {
613
            $params['prerequisite'] = $prerequisites;
614
        }
615
616
        $new_item_id = Database::insert($tbl_lp_item, $params);
617
        if ($new_item_id) {
618
            if ($this->debug > 2) {
619
                error_log('Inserting dir/chapter: '.$new_item_id, 0);
620
            }
621
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
622
            Database::query($sql);
623
624
            if (!empty($next)) {
625
                $sql = "UPDATE $tbl_lp_item
626
                        SET previous_item_id = $new_item_id 
627
                        WHERE c_id = $course_id AND id = $next";
628
                Database::query($sql);
629
            }
630
631
            // Update the item that should be before the new item.
632
            if (!empty($tmp_previous)) {
633
                $sql = "UPDATE $tbl_lp_item
634
                        SET next_item_id = $new_item_id
635
                        WHERE c_id = $course_id AND id = $tmp_previous";
636
                Database::query($sql);
637
            }
638
639
            // Update all the items after the new item.
640
            $sql = "UPDATE $tbl_lp_item
641
                        SET display_order = display_order + 1
642
                    WHERE
643
                        c_id = $course_id AND
644
                        lp_id = ".$this->get_id()." AND
645
                        iid <> $new_item_id AND
646
                        parent_item_id = $parent AND
647
                        display_order > $display_order";
648
            Database::query($sql);
649
650
            // Update the item that should come after the new item.
651
            $sql = "UPDATE $tbl_lp_item
652
                    SET ref = $new_item_id
653
                    WHERE c_id = $course_id AND iid = $new_item_id";
654
            Database::query($sql);
655
656
            // Upload audio.
657
            if (!empty($_FILES['mp3']['name'])) {
658
                // Create the audio folder if it does not exist yet.
659
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
660
                if (!is_dir($filepath.'audio')) {
661
                    mkdir(
662
                        $filepath.'audio',
663
                        api_get_permissions_for_new_directories()
664
                    );
665
                    $audio_id = DocumentManager::addDocument(
666
                        $_course,
667
                        '/audio',
668
                        'folder',
669
                        0,
670
                        'audio',
671
                        '',
672
                        0,
673
                        true,
674
                        null,
675
                        $sessionId,
676
                        $userId
677
                    );
678
                }
679
680
                $file_path = handle_uploaded_document(
681
                    $_course,
682
                    $_FILES['mp3'],
683
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
684
                    '/audio',
685
                    $userId,
686
                    '',
687
                    '',
688
                    '',
689
                    '',
690
                    false
691
                );
692
693
                // Getting the filename only.
694
                $file_components = explode('/', $file_path);
695
                $file = $file_components[count($file_components) - 1];
696
697
                // Store the mp3 file in the lp_item table.
698
                $sql = "UPDATE $tbl_lp_item SET
699
                          audio = '".Database::escape_string($file)."'
700
                        WHERE iid = '".intval($new_item_id)."'";
701
                Database::query($sql);
702
            }
703
        }
704
705
        return $new_item_id;
706
    }
707
708
    /**
709
     * Static admin function allowing addition of a learnpath to a course.
710
     *
711
     * @param string $courseCode
712
     * @param string $name
713
     * @param string $description
714
     * @param string $learnpath
715
     * @param string $origin
716
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
717
     * @param string $publicated_on
718
     * @param string $expired_on
719
     * @param int    $categoryId
720
     * @param int    $userId
721
     *
722
     * @return int The new learnpath ID on success, 0 on failure
723
     */
724
    public static function add_lp(
725
        $courseCode,
726
        $name,
727
        $description = '',
728
        $learnpath = 'guess',
729
        $origin = 'zip',
730
        $zipname = '',
731
        $publicated_on = '',
732
        $expired_on = '',
733
        $categoryId = 0,
734
        $userId = 0
735
    ) {
736
        global $charset;
737
738
        if (!empty($courseCode)) {
739
            $courseInfo = api_get_course_info($courseCode);
740
            $course_id = $courseInfo['real_id'];
741
        } else {
742
            $course_id = api_get_course_int_id();
743
            $courseInfo = api_get_course_info();
744
        }
745
746
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
747
        // Check course code exists.
748
        // Check lp_name doesn't exist, otherwise append something.
749
        $i = 0;
750
        $name = Database::escape_string($name);
751
        $categoryId = (int) $categoryId;
752
753
        // Session id.
754
        $session_id = api_get_session_id();
755
        $userId = empty($userId) ? api_get_user_id() : $userId;
756
        $check_name = "SELECT * FROM $tbl_lp
757
                       WHERE c_id = $course_id AND name = '$name'";
758
759
        $res_name = Database::query($check_name);
760
761
        if (empty($publicated_on)) {
762
            $publicated_on = null;
763
        } else {
764
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
765
        }
766
767
        if (empty($expired_on)) {
768
            $expired_on = null;
769
        } else {
770
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
771
        }
772
773
        while (Database::num_rows($res_name)) {
774
            // There is already one such name, update the current one a bit.
775
            $i++;
776
            $name = $name.' - '.$i;
777
            $check_name = "SELECT * FROM $tbl_lp 
778
                           WHERE c_id = $course_id AND name = '$name'";
779
            $res_name = Database::query($check_name);
780
        }
781
        // New name does not exist yet; keep it.
782
        // Escape description.
783
        // Kevin: added htmlentities().
784
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
785
        $type = 1;
786
        switch ($learnpath) {
787
            case 'guess':
788
                break;
789
            case 'dokeos':
790
            case 'chamilo':
791
                $type = 1;
792
                break;
793
            case 'aicc':
794
                break;
795
        }
796
797
        switch ($origin) {
798
            case 'zip':
799
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
800
                break;
801
            case 'manual':
802
            default:
803
                $get_max = "SELECT MAX(display_order) 
804
                            FROM $tbl_lp WHERE c_id = $course_id";
805
                $res_max = Database::query($get_max);
806
                if (Database::num_rows($res_max) < 1) {
807
                    $dsp = 1;
808
                } else {
809
                    $row = Database::fetch_array($res_max);
810
                    $dsp = $row[0] + 1;
811
                }
812
813
                $params = [
814
                    'c_id' => $course_id,
815
                    'lp_type' => $type,
816
                    'name' => $name,
817
                    'description' => $description,
818
                    'path' => '',
819
                    'default_view_mod' => 'embedded',
820
                    'default_encoding' => 'UTF-8',
821
                    'display_order' => $dsp,
822
                    'content_maker' => 'Chamilo',
823
                    'content_local' => 'local',
824
                    'js_lib' => '',
825
                    'session_id' => $session_id,
826
                    'created_on' => api_get_utc_datetime(),
827
                    'modified_on' => api_get_utc_datetime(),
828
                    'publicated_on' => $publicated_on,
829
                    'expired_on' => $expired_on,
830
                    'category_id' => $categoryId,
831
                    'force_commit' => 0,
832
                    'content_license' => '',
833
                    'debug' => 0,
834
                    'theme' => '',
835
                    'preview_image' => '',
836
                    'author' => '',
837
                    'prerequisite' => 0,
838
                    'hide_toc_frame' => 0,
839
                    'seriousgame_mode' => 0,
840
                    'autolaunch' => 0,
841
                    'max_attempts' => 0,
842
                    'subscribe_users' => 0,
843
                    'accumulate_scorm_time' => 1,
844
                ];
845
                $id = Database::insert($tbl_lp, $params);
846
847
                if ($id > 0) {
848
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
849
                    Database::query($sql);
850
851
                    // Insert into item_property.
852
                    api_item_property_update(
853
                        $courseInfo,
854
                        TOOL_LEARNPATH,
855
                        $id,
856
                        'LearnpathAdded',
857
                        $userId
858
                    );
859
                    api_set_default_visibility(
860
                        $id,
861
                        TOOL_LEARNPATH,
862
                        0,
863
                        $courseInfo,
864
                        $session_id,
865
                        $userId
866
                    );
867
868
                    return $id;
869
                }
870
                break;
871
        }
872
    }
873
874
    /**
875
     * Auto completes the parents of an item in case it's been completed or passed.
876
     *
877
     * @param int $item Optional ID of the item from which to look for parents
878
     */
879
    public function autocomplete_parents($item)
880
    {
881
        $debug = $this->debug;
882
883
        if ($debug) {
884
            error_log('Learnpath::autocomplete_parents()');
885
        }
886
887
        if (empty($item)) {
888
            $item = $this->current;
889
        }
890
891
        $currentItem = $this->getItem($item);
892
        if ($currentItem) {
893
            $parent_id = $currentItem->get_parent();
894
            $parent = $this->getItem($parent_id);
895
            if ($parent) {
896
                // if $item points to an object and there is a parent.
897
                if ($debug) {
898
                    error_log(
899
                        'Autocompleting parent of item '.$item.' '.
900
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
901
                        0
902
                    );
903
                }
904
905
                // New experiment including failed and browsed in completed status.
906
                //$current_status = $currentItem->get_status();
907
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
908
                // Fixes chapter auto complete
909
                if (true) {
910
                    // If the current item is completed or passes or succeeded.
911
                    $updateParentStatus = true;
912
                    if ($debug) {
913
                        error_log('Status of current item is alright');
914
                    }
915
916
                    foreach ($parent->get_children() as $childItemId) {
917
                        $childItem = $this->getItem($childItemId);
918
919
                        // If children was not set try to get the info
920
                        if (empty($childItem->db_item_view_id)) {
921
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
922
                        }
923
924
                        // Check all his brothers (parent's children) for completion status.
925
                        if ($childItemId != $item) {
926
                            if ($debug) {
927
                                error_log(
928
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
929
                                    0
930
                                );
931
                            }
932
                            // Trying completing parents of failed and browsed items as well.
933
                            if ($childItem->status_is(
934
                                [
935
                                    'completed',
936
                                    'passed',
937
                                    'succeeded',
938
                                    'browsed',
939
                                    'failed',
940
                                ]
941
                            )
942
                            ) {
943
                                // Keep completion status to true.
944
                                continue;
945
                            } else {
946
                                if ($debug > 2) {
947
                                    error_log(
948
                                        '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,
949
                                        0
950
                                    );
951
                                }
952
                                $updateParentStatus = false;
953
                                break;
954
                            }
955
                        }
956
                    }
957
958
                    if ($updateParentStatus) {
959
                        // If all the children were completed:
960
                        $parent->set_status('completed');
961
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
962
                        // Force the status to "completed"
963
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
964
                        $this->update_queue[$parent->get_id()] = 'completed';
965
                        if ($debug) {
966
                            error_log(
967
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
968
                                print_r($this->update_queue, 1),
969
                                0
970
                            );
971
                        }
972
                        // Recursive call.
973
                        $this->autocomplete_parents($parent->get_id());
974
                    }
975
                }
976
            } else {
977
                if ($debug) {
978
                    error_log("Parent #$parent_id does not exists");
979
                }
980
            }
981
        } else {
982
            if ($debug) {
983
                error_log("#$item is an item that doesn't have parents");
984
            }
985
        }
986
    }
987
988
    /**
989
     * Closes the current resource.
990
     *
991
     * Stops the timer
992
     * Saves into the database if required
993
     * Clears the current resource data from this object
994
     *
995
     * @return bool True on success, false on failure
996
     */
997
    public function close()
998
    {
999
        if ($this->debug > 0) {
1000
            error_log('In learnpath::close()', 0);
1001
        }
1002
        if (empty($this->lp_id)) {
1003
            $this->error = 'Trying to close this learnpath but no ID is set';
1004
1005
            return false;
1006
        }
1007
        $this->current_time_stop = time();
1008
        $this->ordered_items = [];
1009
        $this->index = 0;
1010
        unset($this->lp_id);
1011
        //unset other stuff
1012
        return true;
1013
    }
1014
1015
    /**
1016
     * Static admin function allowing removal of a learnpath.
1017
     *
1018
     * @param array  $courseInfo
1019
     * @param int    $id         Learnpath ID
1020
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1021
     *
1022
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1023
     */
1024
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1025
    {
1026
        $course_id = api_get_course_int_id();
1027
        if (!empty($courseInfo)) {
1028
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1029
        }
1030
1031
        // TODO: Implement a way of getting this to work when the current object is not set.
1032
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1033
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1034
        if (!empty($id) && ($id != $this->lp_id)) {
1035
            return false;
1036
        }
1037
1038
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1039
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1040
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1041
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1042
1043
        // Delete lp item id.
1044
        foreach ($this->items as $lpItemId => $dummy) {
1045
            $sql = "DELETE FROM $lp_item_view
1046
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1047
            Database::query($sql);
1048
        }
1049
1050
        // Proposed by Christophe (nickname: clefevre)
1051
        $sql = "DELETE FROM $lp_item
1052
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1053
        Database::query($sql);
1054
1055
        $sql = "DELETE FROM $lp_view 
1056
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1057
        Database::query($sql);
1058
1059
        self::toggle_publish($this->lp_id, 'i');
1060
1061
        if ($this->type == 2 || $this->type == 3) {
1062
            // This is a scorm learning path, delete the files as well.
1063
            $sql = "SELECT path FROM $lp
1064
                    WHERE iid = ".$this->lp_id;
1065
            $res = Database::query($sql);
1066
            if (Database::num_rows($res) > 0) {
1067
                $row = Database::fetch_array($res);
1068
                $path = $row['path'];
1069
                $sql = "SELECT id FROM $lp
1070
                        WHERE 
1071
                            c_id = $course_id AND
1072
                            path = '$path' AND 
1073
                            iid != ".$this->lp_id;
1074
                $res = Database::query($sql);
1075
                if (Database::num_rows($res) > 0) {
1076
                    // Another learning path uses this directory, so don't delete it.
1077
                    if ($this->debug > 2) {
1078
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1079
                    }
1080
                } else {
1081
                    // No other LP uses that directory, delete it.
1082
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1083
                    // The absolute system path for this course.
1084
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1085
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1086
                        if ($this->debug > 2) {
1087
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1088
                        }
1089
                        // Proposed by Christophe (clefevre).
1090
                        if (strcmp(substr($path, -2), "/.") == 0) {
1091
                            $path = substr($path, 0, -1); // Remove "." at the end.
1092
                        }
1093
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1094
                        rmdirr($course_scorm_dir.$path);
1095
                    }
1096
                }
1097
            }
1098
        }
1099
1100
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1101
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1102
        // Delete tools
1103
        $sql = "DELETE FROM $tbl_tool
1104
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1105
        Database::query($sql);
1106
1107
        $sql = "DELETE FROM $lp 
1108
                WHERE iid = ".$this->lp_id;
1109
        Database::query($sql);
1110
        // Updates the display order of all lps.
1111
        $this->update_display_order();
1112
1113
        api_item_property_update(
1114
            api_get_course_info(),
1115
            TOOL_LEARNPATH,
1116
            $this->lp_id,
1117
            'delete',
1118
            api_get_user_id()
1119
        );
1120
1121
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1122
            api_get_course_id(),
1123
            4,
1124
            $id,
1125
            api_get_session_id()
1126
        );
1127
1128
        if ($link_info !== false) {
1129
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1130
        }
1131
1132
        if (api_get_setting('search_enabled') == 'true') {
1133
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1134
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1135
        }
1136
    }
1137
1138
    /**
1139
     * Removes all the children of one item - dangerous!
1140
     *
1141
     * @param int $id Element ID of which children have to be removed
1142
     *
1143
     * @return int Total number of children removed
1144
     */
1145
    public function delete_children_items($id)
1146
    {
1147
        $course_id = $this->course_info['real_id'];
1148
        if ($this->debug > 0) {
1149
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1150
        }
1151
        $num = 0;
1152
        if (empty($id) || $id != strval(intval($id))) {
1153
            return false;
1154
        }
1155
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1156
        $sql = "SELECT * FROM $lp_item 
1157
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1158
        $res = Database::query($sql);
1159
        while ($row = Database::fetch_array($res)) {
1160
            $num += $this->delete_children_items($row['iid']);
1161
            $sql = "DELETE FROM $lp_item 
1162
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1163
            Database::query($sql);
1164
            $num++;
1165
        }
1166
1167
        return $num;
1168
    }
1169
1170
    /**
1171
     * Removes an item from the current learnpath.
1172
     *
1173
     * @param int $id Elem ID (0 if first)
1174
     *
1175
     * @return int Number of elements moved
1176
     *
1177
     * @todo implement resource removal
1178
     */
1179
    public function delete_item($id)
1180
    {
1181
        $course_id = api_get_course_int_id();
1182
        if ($this->debug > 0) {
1183
            error_log('In learnpath::delete_item()', 0);
1184
        }
1185
        // TODO: Implement the resource removal.
1186
        if (empty($id) || $id != strval(intval($id))) {
1187
            return false;
1188
        }
1189
        // First select item to get previous, next, and display order.
1190
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1191
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1192
        $res_sel = Database::query($sql_sel);
1193
        if (Database::num_rows($res_sel) < 1) {
1194
            return false;
1195
        }
1196
        $row = Database::fetch_array($res_sel);
1197
        $previous = $row['previous_item_id'];
1198
        $next = $row['next_item_id'];
1199
        $display = $row['display_order'];
1200
        $parent = $row['parent_item_id'];
1201
        $lp = $row['lp_id'];
1202
        // Delete children items.
1203
        $num = $this->delete_children_items($id);
1204
        if ($this->debug > 2) {
1205
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1206
        }
1207
        // Now delete the item.
1208
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1209
        if ($this->debug > 2) {
1210
            error_log('Deleting item: '.$sql_del, 0);
1211
        }
1212
        Database::query($sql_del);
1213
        // Now update surrounding items.
1214
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1215
                    WHERE iid = $previous";
1216
        Database::query($sql_upd);
1217
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1218
                    WHERE iid = $next";
1219
        Database::query($sql_upd);
1220
        // Now update all following items with new display order.
1221
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1222
                    WHERE 
1223
                        c_id = $course_id AND 
1224
                        lp_id = $lp AND 
1225
                        parent_item_id = $parent AND 
1226
                        display_order > $display";
1227
        Database::query($sql_all);
1228
1229
        //Removing prerequisites since the item will not longer exist
1230
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1231
                    WHERE c_id = $course_id AND prerequisite = $id";
1232
        Database::query($sql_all);
1233
1234
        // Remove from search engine if enabled.
1235
        if (api_get_setting('search_enabled') === 'true') {
1236
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1237
            $sql = 'SELECT * FROM %s 
1238
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1239
                    LIMIT 1';
1240
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1241
            $res = Database::query($sql);
1242
            if (Database::num_rows($res) > 0) {
1243
                $row2 = Database::fetch_array($res);
1244
                $di = new ChamiloIndexer();
1245
                $di->remove_document($row2['search_did']);
1246
            }
1247
            $sql = 'DELETE FROM %s 
1248
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1249
                    LIMIT 1';
1250
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1251
            Database::query($sql);
1252
        }
1253
    }
1254
1255
    /**
1256
     * Updates an item's content in place.
1257
     *
1258
     * @param int    $id               Element ID
1259
     * @param int    $parent           Parent item ID
1260
     * @param int    $previous         Previous item ID
1261
     * @param string $title            Item title
1262
     * @param string $description      Item description
1263
     * @param string $prerequisites    Prerequisites (optional)
1264
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1265
     * @param int    $max_time_allowed
1266
     * @param string $url
1267
     *
1268
     * @return bool True on success, false on error
1269
     */
1270
    public function edit_item(
1271
        $id,
1272
        $parent,
1273
        $previous,
1274
        $title,
1275
        $description,
1276
        $prerequisites = '0',
1277
        $audio = [],
1278
        $max_time_allowed = 0,
1279
        $url = ''
1280
    ) {
1281
        $course_id = api_get_course_int_id();
1282
        $_course = api_get_course_info();
1283
1284
        if ($this->debug > 0) {
1285
            error_log('In learnpath::edit_item()', 0);
1286
        }
1287
        if (empty($max_time_allowed)) {
1288
            $max_time_allowed = 0;
1289
        }
1290
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1291
            return false;
1292
        }
1293
1294
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1295
        $sql = "SELECT * FROM $tbl_lp_item 
1296
                WHERE iid = $id";
1297
        $res_select = Database::query($sql);
1298
        $row_select = Database::fetch_array($res_select);
1299
        $audio_update_sql = '';
1300
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1301
            // Create the audio folder if it does not exist yet.
1302
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1303
            if (!is_dir($filepath.'audio')) {
1304
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1305
                $audio_id = DocumentManager::addDocument(
1306
                    $_course,
1307
                    '/audio',
1308
                    'folder',
1309
                    0,
1310
                    'audio'
1311
                );
1312
            }
1313
1314
            // Upload file in documents.
1315
            $pi = pathinfo($audio['name']);
1316
            if ($pi['extension'] == 'mp3') {
1317
                $c_det = api_get_course_info($this->cc);
1318
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1319
                $path = handle_uploaded_document(
1320
                    $c_det,
1321
                    $audio,
1322
                    $bp,
1323
                    '/audio',
1324
                    api_get_user_id(),
1325
                    0,
1326
                    null,
1327
                    0,
1328
                    'rename',
1329
                    false,
1330
                    0
1331
                );
1332
                $path = substr($path, 7);
1333
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1334
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1335
            }
1336
        }
1337
1338
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1339
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1340
1341
        // TODO: htmlspecialchars to be checked for encoding related problems.
1342
        if ($same_parent && $same_previous) {
1343
            // Only update title and description.
1344
            $sql = "UPDATE $tbl_lp_item
1345
                    SET title = '".Database::escape_string($title)."',
1346
                        prerequisite = '".$prerequisites."',
1347
                        description = '".Database::escape_string($description)."'
1348
                        ".$audio_update_sql.",
1349
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1350
                    WHERE iid = $id";
1351
            Database::query($sql);
1352
        } else {
1353
            $old_parent = $row_select['parent_item_id'];
1354
            $old_previous = $row_select['previous_item_id'];
1355
            $old_next = $row_select['next_item_id'];
1356
            $old_order = $row_select['display_order'];
1357
            $old_prerequisite = $row_select['prerequisite'];
1358
            $old_max_time_allowed = $row_select['max_time_allowed'];
1359
1360
            /* BEGIN -- virtually remove the current item id */
1361
            /* for the next and previous item it is like the current item doesn't exist anymore */
1362
            if ($old_previous != 0) {
1363
                // Next
1364
                $sql = "UPDATE $tbl_lp_item
1365
                        SET next_item_id = $old_next
1366
                        WHERE iid = $old_previous";
1367
                Database::query($sql);
1368
            }
1369
1370
            if (!empty($old_next)) {
1371
                // Previous
1372
                $sql = "UPDATE $tbl_lp_item
1373
                        SET previous_item_id = $old_previous
1374
                        WHERE iid = $old_next";
1375
                Database::query($sql);
1376
            }
1377
1378
            // display_order - 1 for every item with a display_order
1379
            // bigger then the display_order of the current item.
1380
            $sql = "UPDATE $tbl_lp_item
1381
                    SET display_order = display_order - 1
1382
                    WHERE
1383
                        c_id = $course_id AND
1384
                        display_order > $old_order AND
1385
                        lp_id = ".$this->lp_id." AND
1386
                        parent_item_id = $old_parent";
1387
            Database::query($sql);
1388
            /* END -- virtually remove the current item id */
1389
1390
            /* BEGIN -- update the current item id to his new location */
1391
            if ($previous == 0) {
1392
                // Select the data of the item that should come after the current item.
1393
                $sql = "SELECT id, display_order
1394
                        FROM $tbl_lp_item
1395
                        WHERE
1396
                            c_id = $course_id AND
1397
                            lp_id = ".$this->lp_id." AND
1398
                            parent_item_id = $parent AND
1399
                            previous_item_id = $previous";
1400
                $res_select_old = Database::query($sql);
1401
                $row_select_old = Database::fetch_array($res_select_old);
1402
1403
                // If the new parent didn't have children before.
1404
                if (Database::num_rows($res_select_old) == 0) {
1405
                    $new_next = 0;
1406
                    $new_order = 1;
1407
                } else {
1408
                    $new_next = $row_select_old['id'];
1409
                    $new_order = $row_select_old['display_order'];
1410
                }
1411
            } else {
1412
                // Select the data of the item that should come before the current item.
1413
                $sql = "SELECT next_item_id, display_order
1414
                        FROM $tbl_lp_item
1415
                        WHERE iid = $previous";
1416
                $res_select_old = Database::query($sql);
1417
                $row_select_old = Database::fetch_array($res_select_old);
1418
                $new_next = $row_select_old['next_item_id'];
1419
                $new_order = $row_select_old['display_order'] + 1;
1420
            }
1421
1422
            // TODO: htmlspecialchars to be checked for encoding related problems.
1423
            // Update the current item with the new data.
1424
            $sql = "UPDATE $tbl_lp_item
1425
                    SET
1426
                        title = '".Database::escape_string($title)."',
1427
                        description = '".Database::escape_string($description)."',
1428
                        parent_item_id = $parent,
1429
                        previous_item_id = $previous,
1430
                        next_item_id = $new_next,
1431
                        display_order = $new_order
1432
                        $audio_update_sql
1433
                    WHERE iid = $id";
1434
            Database::query($sql);
1435
1436
            if ($previous != 0) {
1437
                // Update the previous item's next_item_id.
1438
                $sql = "UPDATE $tbl_lp_item
1439
                        SET next_item_id = $id
1440
                        WHERE iid = $previous";
1441
                Database::query($sql);
1442
            }
1443
1444
            if (!empty($new_next)) {
1445
                // Update the next item's previous_item_id.
1446
                $sql = "UPDATE $tbl_lp_item
1447
                        SET previous_item_id = $id
1448
                        WHERE iid = $new_next";
1449
                Database::query($sql);
1450
            }
1451
1452
            if ($old_prerequisite != $prerequisites) {
1453
                $sql = "UPDATE $tbl_lp_item
1454
                        SET prerequisite = '$prerequisites'
1455
                        WHERE iid = $id";
1456
                Database::query($sql);
1457
            }
1458
1459
            if ($old_max_time_allowed != $max_time_allowed) {
1460
                // update max time allowed
1461
                $sql = "UPDATE $tbl_lp_item
1462
                        SET max_time_allowed = $max_time_allowed
1463
                        WHERE iid = $id";
1464
                Database::query($sql);
1465
            }
1466
1467
            // Update all the items with the same or a bigger display_order than the current item.
1468
            $sql = "UPDATE $tbl_lp_item
1469
                    SET display_order = display_order + 1
1470
                    WHERE
1471
                       c_id = $course_id AND
1472
                       lp_id = ".$this->get_id()." AND
1473
                       iid <> $id AND
1474
                       parent_item_id = $parent AND
1475
                       display_order >= $new_order";
1476
            Database::query($sql);
1477
        }
1478
1479
        if ($row_select['item_type'] == 'link') {
1480
            $link = new Link();
1481
            $linkId = $row_select['path'];
1482
            $link->updateLink($linkId, $url);
1483
        }
1484
    }
1485
1486
    /**
1487
     * Updates an item's prereq in place.
1488
     *
1489
     * @param int    $id              Element ID
1490
     * @param string $prerequisite_id Prerequisite Element ID
1491
     * @param int    $mastery_score   Prerequisite min score
1492
     * @param int    $max_score       Prerequisite max score
1493
     *
1494
     * @return bool True on success, false on error
1495
     */
1496
    public function edit_item_prereq(
1497
        $id,
1498
        $prerequisite_id,
1499
        $mastery_score = 0,
1500
        $max_score = 100
1501
    ) {
1502
        $course_id = api_get_course_int_id();
1503
        if ($this->debug > 0) {
1504
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$mastery_score.','.$max_score.')', 0);
1505
        }
1506
1507
        if (empty($id) || ($id != strval(intval($id))) || empty($prerequisite_id)) {
1508
            return false;
1509
        }
1510
1511
        $prerequisite_id = (int) $prerequisite_id;
1512
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1513
1514
        if (!is_numeric($mastery_score) || $mastery_score < 0) {
1515
            $mastery_score = 0;
1516
        }
1517
1518
        if (!is_numeric($max_score) || $max_score < 0) {
1519
            $max_score = 100;
1520
        }
1521
1522
        /*if ($mastery_score > $max_score) {
1523
            $max_score = $mastery_score;
1524
        }*/
1525
1526
        if (!is_numeric($prerequisite_id)) {
1527
            $prerequisite_id = 'NULL';
1528
        }
1529
1530
        $mastery_score = floatval($mastery_score);
1531
        $max_score = floatval($max_score);
1532
1533
        $sql = " UPDATE $tbl_lp_item
1534
                 SET
1535
                    prerequisite = $prerequisite_id ,
1536
                    prerequisite_min_score = $mastery_score ,
1537
                    prerequisite_max_score = $max_score
1538
                 WHERE iid = $id";
1539
        Database::query($sql);
1540
        // TODO: Update the item object (can be ignored for now because refreshed).
1541
        return true;
1542
    }
1543
1544
    /**
1545
     * Gets all the chapters belonging to the same parent as the item/chapter given
1546
     * Can also be called as abstract method.
1547
     *
1548
     * @param int $id Item ID
1549
     *
1550
     * @return array A list of all the "brother items" (or an empty array on failure)
1551
     */
1552
    public function getSiblingDirectories($id)
1553
    {
1554
        $course_id = api_get_course_int_id();
1555
        if ($this->debug > 0) {
1556
            error_log('In learnpath::getSiblingDirectories()', 0);
1557
        }
1558
1559
        if (empty($id) || $id != strval(intval($id))) {
1560
            return [];
1561
        }
1562
1563
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1564
        $sql_parent = "SELECT * FROM $lp_item
1565
                       WHERE iid = $id AND item_type='dir'";
1566
        $res_parent = Database::query($sql_parent);
1567
        if (Database::num_rows($res_parent) > 0) {
1568
            $row_parent = Database::fetch_array($res_parent);
1569
            $parent = $row_parent['parent_item_id'];
1570
            $sql = "SELECT * FROM $lp_item
1571
                    WHERE
1572
                        parent_item_id = $parent AND
1573
                        iid = $id AND
1574
                        item_type='dir'
1575
                    ORDER BY display_order";
1576
            $res_bros = Database::query($sql);
1577
1578
            $list = [];
1579
            while ($row_bro = Database::fetch_array($res_bros)) {
1580
                $list[] = $row_bro;
1581
            }
1582
1583
            return $list;
1584
        }
1585
1586
        return [];
1587
    }
1588
1589
    /**
1590
     * Gets all the items belonging to the same parent as the item given
1591
     * Can also be called as abstract method.
1592
     *
1593
     * @param int $id Item ID
1594
     *
1595
     * @return array A list of all the "brother items" (or an empty array on failure)
1596
     */
1597
    public function get_brother_items($id)
1598
    {
1599
        $course_id = api_get_course_int_id();
1600
        if ($this->debug > 0) {
1601
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1602
        }
1603
1604
        if (empty($id) || $id != strval(intval($id))) {
1605
            return [];
1606
        }
1607
1608
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1609
        $sql_parent = "SELECT * FROM $lp_item 
1610
                       WHERE iid = $id";
1611
        $res_parent = Database::query($sql_parent);
1612
        if (Database::num_rows($res_parent) > 0) {
1613
            $row_parent = Database::fetch_array($res_parent);
1614
            $parent = $row_parent['parent_item_id'];
1615
            $sql = "SELECT * FROM $lp_item 
1616
                    WHERE c_id = $course_id AND parent_item_id = $parent
1617
                    ORDER BY display_order";
1618
            $res_bros = Database::query($sql);
1619
            $list = [];
1620
            while ($row_bro = Database::fetch_array($res_bros)) {
1621
                $list[] = $row_bro;
1622
            }
1623
1624
            return $list;
1625
        }
1626
1627
        return [];
1628
    }
1629
1630
    /**
1631
     * Get the specific prefix index terms of this learning path.
1632
     *
1633
     * @param string $prefix
1634
     *
1635
     * @return array Array of terms
1636
     */
1637
    public function get_common_index_terms_by_prefix($prefix)
1638
    {
1639
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1640
        $terms = get_specific_field_values_list_by_prefix(
1641
            $prefix,
1642
            $this->cc,
1643
            TOOL_LEARNPATH,
1644
            $this->lp_id
1645
        );
1646
        $prefix_terms = [];
1647
        if (!empty($terms)) {
1648
            foreach ($terms as $term) {
1649
                $prefix_terms[] = $term['value'];
1650
            }
1651
        }
1652
1653
        return $prefix_terms;
1654
    }
1655
1656
    /**
1657
     * Gets the number of items currently completed.
1658
     *
1659
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1660
     *
1661
     * @return int The number of items currently completed
1662
     */
1663
    public function get_complete_items_count($failedStatusException = false)
1664
    {
1665
        if ($this->debug > 0) {
1666
            error_log('In learnpath::get_complete_items_count()', 0);
1667
        }
1668
        $i = 0;
1669
        $completedStatusList = [
1670
            'completed',
1671
            'passed',
1672
            'succeeded',
1673
            'browsed',
1674
        ];
1675
1676
        if (!$failedStatusException) {
1677
            $completedStatusList[] = 'failed';
1678
        }
1679
1680
        foreach ($this->items as $id => $dummy) {
1681
            // Trying failed and browsed considered "progressed" as well.
1682
            if ($this->items[$id]->status_is($completedStatusList) &&
1683
                $this->items[$id]->get_type() != 'dir'
1684
            ) {
1685
                $i++;
1686
            }
1687
        }
1688
1689
        return $i;
1690
    }
1691
1692
    /**
1693
     * Gets the current item ID.
1694
     *
1695
     * @return int The current learnpath item id
1696
     */
1697
    public function get_current_item_id()
1698
    {
1699
        $current = 0;
1700
        if ($this->debug > 0) {
1701
            error_log('In learnpath::get_current_item_id()', 0);
1702
        }
1703
        if (!empty($this->current)) {
1704
            $current = $this->current;
1705
        }
1706
        if ($this->debug > 2) {
1707
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1708
        }
1709
1710
        return $current;
1711
    }
1712
1713
    /**
1714
     * Force to get the first learnpath item id.
1715
     *
1716
     * @return int The current learnpath item id
1717
     */
1718
    public function get_first_item_id()
1719
    {
1720
        $current = 0;
1721
        if (is_array($this->ordered_items)) {
1722
            $current = $this->ordered_items[0];
1723
        }
1724
1725
        return $current;
1726
    }
1727
1728
    /**
1729
     * Gets the total number of items available for viewing in this SCORM.
1730
     *
1731
     * @return int The total number of items
1732
     */
1733
    public function get_total_items_count()
1734
    {
1735
        if ($this->debug > 0) {
1736
            error_log('In learnpath::get_total_items_count()', 0);
1737
        }
1738
1739
        return count($this->items);
1740
    }
1741
1742
    /**
1743
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1744
     *
1745
     * @return int The total no-chapters number of items
1746
     */
1747
    public function getTotalItemsCountWithoutDirs()
1748
    {
1749
        if ($this->debug > 0) {
1750
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1751
        }
1752
        $total = 0;
1753
        $typeListNotToCount = self::getChapterTypes();
1754
        foreach ($this->items as $temp2) {
1755
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1756
                $total++;
1757
            }
1758
        }
1759
1760
        return $total;
1761
    }
1762
1763
    /**
1764
     *  Sets the first element URL.
1765
     */
1766
    public function first()
1767
    {
1768
        if ($this->debug > 0) {
1769
            error_log('In learnpath::first()', 0);
1770
            error_log('$this->last_item_seen '.$this->last_item_seen);
1771
        }
1772
1773
        // Test if the last_item_seen exists and is not a dir.
1774
        if (count($this->ordered_items) == 0) {
1775
            $this->index = 0;
1776
        }
1777
1778
        if (!empty($this->last_item_seen) &&
1779
            !empty($this->items[$this->last_item_seen]) &&
1780
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1781
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1782
            //&& !$this->items[$this->last_item_seen]->is_done()
1783
        ) {
1784
            if ($this->debug > 2) {
1785
                error_log(
1786
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1787
                    $this->items[$this->last_item_seen]->get_type()
1788
                );
1789
            }
1790
            $index = -1;
1791
            foreach ($this->ordered_items as $myindex => $item_id) {
1792
                if ($item_id == $this->last_item_seen) {
1793
                    $index = $myindex;
1794
                    break;
1795
                }
1796
            }
1797
            if ($index == -1) {
1798
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1799
                if ($this->debug > 2) {
1800
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1801
                }
1802
1803
                return false;
1804
            } else {
1805
                $this->last = $this->last_item_seen;
1806
                $this->current = $this->last_item_seen;
1807
                $this->index = $index;
1808
            }
1809
        } else {
1810
            if ($this->debug > 2) {
1811
                error_log('In learnpath::first() - No last item seen', 0);
1812
            }
1813
            $index = 0;
1814
            // Loop through all ordered items and stop at the first item that is
1815
            // not a directory *and* that has not been completed yet.
1816
            while (!empty($this->ordered_items[$index]) &&
1817
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1818
                (
1819
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1820
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1821
                ) && $index < $this->max_ordered_items) {
1822
                $index++;
1823
            }
1824
1825
            $this->last = $this->current;
1826
            // current is
1827
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1828
            $this->index = $index;
1829
            if ($this->debug > 2) {
1830
                error_log('$index '.$index);
1831
                error_log('In learnpath::first() - No last item seen');
1832
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1833
            }
1834
        }
1835
        if ($this->debug > 2) {
1836
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1837
        }
1838
    }
1839
1840
    /**
1841
     * Gets the information about an item in a format usable as JavaScript to update
1842
     * the JS API by just printing this content into the <head> section of the message frame.
1843
     *
1844
     * @param int $item_id
1845
     *
1846
     * @return string
1847
     */
1848
    public function get_js_info($item_id = 0)
1849
    {
1850
        if ($this->debug > 0) {
1851
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1852
        }
1853
1854
        $info = '';
1855
        $item_id = intval($item_id);
1856
1857
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1858
            //if item is defined, return values from DB
1859
            $oItem = $this->items[$item_id];
1860
            $info .= '<script language="javascript">';
1861
            $info .= "top.set_score(".$oItem->get_score().");\n";
1862
            $info .= "top.set_max(".$oItem->get_max().");\n";
1863
            $info .= "top.set_min(".$oItem->get_min().");\n";
1864
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1865
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1866
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1867
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1868
            $info .= "top.set_flag_synchronized();";
1869
            $info .= '</script>';
1870
            if ($this->debug > 2) {
1871
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1872
            }
1873
1874
            return $info;
1875
        } else {
1876
            // If item_id is empty, just update to default SCORM data.
1877
            $info .= '<script language="javascript">';
1878
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1879
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1880
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1881
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1882
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1883
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1884
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1885
            $info .= "top.set_flag_synchronized();";
1886
            $info .= '</script>';
1887
            if ($this->debug > 2) {
1888
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1889
            }
1890
1891
            return $info;
1892
        }
1893
    }
1894
1895
    /**
1896
     * Gets the js library from the database.
1897
     *
1898
     * @return string The name of the javascript library to be used
1899
     */
1900
    public function get_js_lib()
1901
    {
1902
        $lib = '';
1903
        if (!empty($this->js_lib)) {
1904
            $lib = $this->js_lib;
1905
        }
1906
1907
        return $lib;
1908
    }
1909
1910
    /**
1911
     * Gets the learnpath database ID.
1912
     *
1913
     * @return int Learnpath ID in the lp table
1914
     */
1915
    public function get_id()
1916
    {
1917
        if (!empty($this->lp_id)) {
1918
            return $this->lp_id;
1919
        } else {
1920
            return 0;
1921
        }
1922
    }
1923
1924
    /**
1925
     * Gets the last element URL.
1926
     *
1927
     * @return string URL to load into the viewer
1928
     */
1929
    public function get_last()
1930
    {
1931
        if ($this->debug > 0) {
1932
            error_log('In learnpath::get_last()', 0);
1933
        }
1934
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1935
        if (count($this->ordered_items) > 0) {
1936
            $this->index = count($this->ordered_items) - 1;
1937
1938
            return $this->ordered_items[$this->index];
1939
        }
1940
1941
        return false;
1942
    }
1943
1944
    /**
1945
     * Gets the navigation bar for the learnpath display screen.
1946
     *
1947
     * @return string The HTML string to use as a navigation bar
1948
     */
1949
    public function get_navigation_bar($idBar = null, $display = null)
1950
    {
1951
        if ($this->debug > 0) {
1952
            error_log('In learnpath::get_navigation_bar()', 0);
1953
        }
1954
        if (empty($idBar)) {
1955
            $idBar = 'control-top';
1956
        }
1957
        $lpId = $this->lp_id;
1958
        $mycurrentitemid = $this->get_current_item_id();
1959
1960
        $reportingText = get_lang('Reporting');
1961
        $previousText = get_lang('ScormPrevious');
1962
        $nextText = get_lang('ScormNext');
1963
        $fullScreenText = get_lang('ScormExitFullScreen');
1964
1965
        $settings = api_get_configuration_value('lp_view_settings');
1966
        $display = isset($settings['display']) ? $settings['display'] : false;
1967
        $reportingIcon = '
1968
            <a class="icon-toolbar" 
1969
                id="stats_link"
1970
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
1971
                onclick="window.parent.API.save_asset(); return true;" 
1972
                target="content_name" title="'.$reportingText.'">
1973
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1974
            </a>';
1975
1976
        if (!empty($display)) {
1977
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1978
            if ($showReporting == false) {
1979
                $reportingIcon = '';
1980
            }
1981
        }
1982
1983
        $hideArrows = false;
1984
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1985
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1986
        }
1987
1988
        $previousIcon = '';
1989
        $nextIcon = '';
1990
        if ($hideArrows === false) {
1991
            $previousIcon = '
1992
            <a class="icon-toolbar" id="scorm-previous" href="#" 
1993
                onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1994
                <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1995
            </a>';
1996
1997
            $nextIcon = '
1998
            <a class="icon-toolbar" id="scorm-next" href="#" 
1999
                onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2000
                <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2001
            </a>';
2002
        }
2003
2004
        if ($this->mode === 'fullscreen') {
2005
            $navbar = '
2006
                  <span id="'.$idBar.'" class="buttons">
2007
                    '.$reportingIcon.'
2008
                    '.$previousIcon.'                    
2009
                    '.$nextIcon.'
2010
                    <a class="icon-toolbar" id="view-embedded" 
2011
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2012
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2013
                    </a>
2014
                  </span>';
2015
        } else {
2016
            $navbar = '
2017
            <span id="'.$idBar.'" class="buttons text-right">
2018
                '.$reportingIcon.'
2019
                '.$previousIcon.'
2020
                '.$nextIcon.'               
2021
            </span>';
2022
        }
2023
2024
        return $navbar;
2025
    }
2026
2027
    /**
2028
     * Gets the next resource in queue (url).
2029
     *
2030
     * @return string URL to load into the viewer
2031
     */
2032
    public function get_next_index()
2033
    {
2034
        if ($this->debug > 0) {
2035
            error_log('In learnpath::get_next_index()', 0);
2036
        }
2037
        // TODO
2038
        $index = $this->index;
2039
        $index++;
2040
        if ($this->debug > 2) {
2041
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2042
        }
2043
        while (
2044
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2045
            $index < $this->max_ordered_items
2046
        ) {
2047
            $index++;
2048
            if ($index == $this->max_ordered_items) {
2049
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2050
                    return $this->index;
2051
                } else {
2052
                    return $index;
2053
                }
2054
            }
2055
        }
2056
        if (empty($this->ordered_items[$index])) {
2057
            return $this->index;
2058
        }
2059
        if ($this->debug > 2) {
2060
            error_log('index is now '.$index, 0);
2061
        }
2062
2063
        return $index;
2064
    }
2065
2066
    /**
2067
     * Gets item_id for the next element.
2068
     *
2069
     * @return int Next item (DB) ID
2070
     */
2071
    public function get_next_item_id()
2072
    {
2073
        if ($this->debug > 0) {
2074
            error_log('In learnpath::get_next_item_id()', 0);
2075
        }
2076
        $new_index = $this->get_next_index();
2077
        if (!empty($new_index)) {
2078
            if (isset($this->ordered_items[$new_index])) {
2079
                if ($this->debug > 2) {
2080
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2081
                }
2082
2083
                return $this->ordered_items[$new_index];
2084
            }
2085
        }
2086
        if ($this->debug > 2) {
2087
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2088
        }
2089
2090
        return 0;
2091
    }
2092
2093
    /**
2094
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2095
     *
2096
     * Generally, the package provided is in the form of a zip file, so the function
2097
     * has been written to test a zip file. If not a zip, the function will return the
2098
     * default return value: ''
2099
     *
2100
     * @param string $file_path the path to the file
2101
     * @param string $file_name the original name of the file
2102
     *
2103
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2104
     */
2105
    public static function get_package_type($file_path, $file_name)
2106
    {
2107
        // Get name of the zip file without the extension.
2108
        $file_info = pathinfo($file_name);
2109
        $extension = $file_info['extension']; // Extension only.
2110
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2111
                'dll',
2112
                'exe',
2113
            ])) {
2114
            return 'oogie';
2115
        }
2116
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2117
                'dll',
2118
                'exe',
2119
            ])) {
2120
            return 'woogie';
2121
        }
2122
2123
        $zipFile = new PclZip($file_path);
2124
        // Check the zip content (real size and file extension).
2125
        $zipContentArray = $zipFile->listContent();
2126
        $package_type = '';
2127
        $manifest = '';
2128
        $aicc_match_crs = 0;
2129
        $aicc_match_au = 0;
2130
        $aicc_match_des = 0;
2131
        $aicc_match_cst = 0;
2132
2133
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2134
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2135
            foreach ($zipContentArray as $thisContent) {
2136
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2137
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2138
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2139
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2140
                    $package_type = 'scorm';
2141
                    break; // Exit the foreach loop.
2142
                } elseif (
2143
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2144
                    in_array(
2145
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2146
                        ['crs', 'au', 'des', 'cst']
2147
                    )
2148
                ) {
2149
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2150
                    switch ($ext) {
2151
                        case 'crs':
2152
                            $aicc_match_crs = 1;
2153
                            break;
2154
                        case 'au':
2155
                            $aicc_match_au = 1;
2156
                            break;
2157
                        case 'des':
2158
                            $aicc_match_des = 1;
2159
                            break;
2160
                        case 'cst':
2161
                            $aicc_match_cst = 1;
2162
                            break;
2163
                        default:
2164
                            break;
2165
                    }
2166
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2167
                } else {
2168
                    $package_type = '';
2169
                }
2170
            }
2171
        }
2172
2173
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2174
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2175
            $package_type = 'aicc';
2176
        }
2177
2178
        // Try with chamilo course builder
2179
        if (empty($package_type)) {
2180
            $package_type = 'chamilo';
2181
        }
2182
2183
        return $package_type;
2184
    }
2185
2186
    /**
2187
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2188
     *
2189
     * @return string URL to load into the viewer
2190
     */
2191
    public function get_previous_index()
2192
    {
2193
        if ($this->debug > 0) {
2194
            error_log('In learnpath::get_previous_index()', 0);
2195
        }
2196
        $index = $this->index;
2197
        if (isset($this->ordered_items[$index - 1])) {
2198
            $index--;
2199
            while (isset($this->ordered_items[$index]) &&
2200
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2201
            ) {
2202
                $index--;
2203
                if ($index < 0) {
2204
                    return $this->index;
2205
                }
2206
            }
2207
        } else {
2208
            if ($this->debug > 2) {
2209
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2210
            }
2211
            // There is no previous item.
2212
        }
2213
2214
        return $index;
2215
    }
2216
2217
    /**
2218
     * Gets item_id for the next element.
2219
     *
2220
     * @return int Previous item (DB) ID
2221
     */
2222
    public function get_previous_item_id()
2223
    {
2224
        if ($this->debug > 0) {
2225
            error_log('In learnpath::get_previous_item_id()', 0);
2226
        }
2227
        $new_index = $this->get_previous_index();
2228
2229
        return $this->ordered_items[$new_index];
2230
    }
2231
2232
    /**
2233
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2234
     *
2235
     * @param int    $lpItemId
2236
     * @param string $autostart
2237
     *
2238
     * @return string The mediaplayer HTML
2239
     */
2240
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2241
    {
2242
        $course_id = api_get_course_int_id();
2243
        $_course = api_get_course_info();
2244
        if (empty($_course)) {
2245
            return '';
2246
        }
2247
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2248
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2249
        $lpItemId = (int) $lpItemId;
2250
2251
        // Getting all the information about the item.
2252
        $sql = "SELECT * FROM $tbl_lp_item as lpi
2253
                INNER JOIN $tbl_lp_item_view as lp_view
2254
                ON (lpi.iid = lp_view.lp_item_id)
2255
                WHERE
2256
                    lpi.iid = $lpItemId AND
2257
                    lp_view.c_id = $course_id";
2258
        $result = Database::query($sql);
2259
        $row = Database::fetch_assoc($result);
2260
        $output = '';
2261
2262
        if (!empty($row['audio'])) {
2263
            $list = $_SESSION['oLP']->get_toc();
2264
2265
            switch ($row['item_type']) {
2266
                case 'quiz':
2267
                    $type_quiz = false;
2268
2269
                    foreach ($list as $toc) {
2270
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2271
                            $type_quiz = true;
2272
                        }
2273
                    }
2274
2275
                    if ($type_quiz) {
2276
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2277
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2278
                        } else {
2279
                            $autostart_audio = $autostart;
2280
                        }
2281
                    }
2282
                    break;
2283
                case TOOL_READOUT_TEXT:;
2284
                    $autostart_audio = 'false';
2285
                    break;
2286
                default:
2287
                    $autostart_audio = 'true';
2288
            }
2289
2290
            $courseInfo = api_get_course_info();
2291
            $audio = $row['audio'];
2292
2293
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2294
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2295
2296
            if (!file_exists($file)) {
2297
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2298
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2299
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2300
            }
2301
2302
            $player = Display::getMediaPlayer(
2303
                $file,
2304
                [
2305
                    'id' => 'lp_audio_media_player',
2306
                    'url' => $url,
2307
                    'autoplay' => $autostart_audio,
2308
                    'width' => '100%',
2309
                ]
2310
            );
2311
2312
            // The mp3 player.
2313
            $output = '<div id="container">';
2314
            $output .= $player;
2315
            $output .= '</div>';
2316
        }
2317
2318
        return $output;
2319
    }
2320
2321
    /**
2322
     * @param int   $studentId
2323
     * @param int   $prerequisite
2324
     * @param array $courseInfo
2325
     * @param int   $sessionId
2326
     *
2327
     * @return bool
2328
     */
2329
    public static function isBlockedByPrerequisite(
2330
        $studentId,
2331
        $prerequisite,
2332
        $courseInfo,
2333
        $sessionId
2334
    ) {
2335
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2336
        if ($allow) {
2337
            if (api_is_allowed_to_edit() ||
2338
                api_is_platform_admin(true) ||
2339
                api_is_drh() ||
2340
                api_is_coach($sessionId, $courseInfo['real_id'], false)
2341
            ) {
2342
                return false;
2343
            }
2344
        }
2345
2346
        $isBlocked = false;
2347
2348
        if (!empty($prerequisite)) {
2349
            $progress = self::getProgress(
2350
                $prerequisite,
2351
                $studentId,
2352
                $courseInfo['real_id'],
2353
                $sessionId
2354
            );
2355
            if ($progress < 100) {
2356
                $isBlocked = true;
2357
            }
2358
        }
2359
2360
        return $isBlocked;
2361
    }
2362
2363
    /**
2364
     * Checks if the learning path is visible for student after the progress
2365
     * of its prerequisite is completed, considering the time availability and
2366
     * the LP visibility.
2367
     *
2368
     * @param int  $lp_id
2369
     * @param int  $student_id
2370
     * @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...
2371
     * @param int  $sessionId
2372
     *
2373
     * @return bool
2374
     */
2375
    public static function is_lp_visible_for_student(
2376
        $lp_id,
2377
        $student_id,
2378
        $courseCode = null,
2379
        $sessionId = 0
2380
    ) {
2381
        $courseInfo = api_get_course_info($courseCode);
2382
        $lp_id = (int) $lp_id;
2383
        $sessionId = (int) $sessionId;
2384
2385
        if (empty($courseInfo)) {
2386
            return false;
2387
        }
2388
2389
        if (empty($sessionId)) {
2390
            $sessionId = api_get_session_id();
2391
        }
2392
2393
        $itemInfo = api_get_item_property_info(
2394
            $courseInfo['real_id'],
2395
            TOOL_LEARNPATH,
2396
            $lp_id,
2397
            $sessionId
2398
        );
2399
2400
        // If the item was deleted.
2401
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2402
            return false;
2403
        }
2404
2405
        // @todo remove this query and load the row info as a parameter
2406
        $table = Database::get_course_table(TABLE_LP_MAIN);
2407
        // Get current prerequisite
2408
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2409
                FROM $table
2410
                WHERE iid = $lp_id";
2411
        $rs = Database::query($sql);
2412
        $now = time();
2413
        if (Database::num_rows($rs) > 0) {
2414
            $row = Database::fetch_array($rs, 'ASSOC');
2415
            $prerequisite = $row['prerequisite'];
2416
            $is_visible = true;
2417
2418
            $isBlocked = self::isBlockedByPrerequisite(
2419
                $student_id,
2420
                $prerequisite,
2421
                $courseInfo,
2422
                $sessionId
2423
            );
2424
2425
            if ($isBlocked) {
2426
                $is_visible = false;
2427
            }
2428
2429
            // Also check the time availability of the LP
2430
            if ($is_visible) {
2431
                // Adding visibility restrictions
2432
                if (!empty($row['publicated_on'])) {
2433
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2434
                        $is_visible = false;
2435
                    }
2436
                }
2437
                // Blocking empty start times see BT#2800
2438
                global $_custom;
2439
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2440
                    $_custom['lps_hidden_when_no_start_date']
2441
                ) {
2442
                    if (empty($row['publicated_on'])) {
2443
                        $is_visible = false;
2444
                    }
2445
                }
2446
2447
                if (!empty($row['expired_on'])) {
2448
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2449
                        $is_visible = false;
2450
                    }
2451
                }
2452
            }
2453
2454
            $subscriptionSettings = self::getSubscriptionSettings();
2455
2456
            // Check if the subscription users/group to a LP is ON
2457
            if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2458
                $subscriptionSettings['allow_add_users_to_lp'] === true
2459
            ) {
2460
                // Try group
2461
                $is_visible = false;
2462
                // Checking only the user visibility
2463
                $userVisibility = api_get_item_visibility(
2464
                    $courseInfo,
2465
                    'learnpath',
2466
                    $row['id'],
2467
                    $sessionId,
2468
                    $student_id,
2469
                    'LearnpathSubscription'
2470
                );
2471
2472
                if ($userVisibility == 1) {
2473
                    $is_visible = true;
2474
                } else {
2475
                    $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2476
                    if (!empty($userGroups)) {
2477
                        foreach ($userGroups as $groupInfo) {
2478
                            $groupId = $groupInfo['iid'];
2479
                            $userVisibility = api_get_item_visibility(
2480
                                $courseInfo,
2481
                                'learnpath',
2482
                                $row['id'],
2483
                                $sessionId,
2484
                                null,
2485
                                'LearnpathSubscription',
2486
                                $groupId
2487
                            );
2488
2489
                            if ($userVisibility == 1) {
2490
                                $is_visible = true;
2491
                                break;
2492
                            }
2493
                        }
2494
                    }
2495
                }
2496
            }
2497
2498
            return $is_visible;
2499
        }
2500
2501
        return false;
2502
    }
2503
2504
    /**
2505
     * @param int $lpId
2506
     * @param int $userId
2507
     * @param int $courseId
2508
     * @param int $sessionId
2509
     *
2510
     * @return int
2511
     */
2512
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2513
    {
2514
        $lpId = (int) $lpId;
2515
        $userId = (int) $userId;
2516
        $courseId = (int) $courseId;
2517
        $sessionId = (int) $sessionId;
2518
        $progress = 0;
2519
2520
        $sessionCondition = api_get_session_condition($sessionId);
2521
        $table = Database::get_course_table(TABLE_LP_VIEW);
2522
        $sql = "SELECT * FROM $table
2523
                WHERE
2524
                    c_id = $courseId AND
2525
                    lp_id = $lpId AND
2526
                    user_id = $userId $sessionCondition ";
2527
        $res = Database::query($sql);
2528
        if (Database::num_rows($res) > 0) {
2529
            $row = Database:: fetch_array($res);
2530
            $progress = $row['progress'];
2531
        }
2532
2533
        return (int) $progress;
2534
    }
2535
2536
    /**
2537
     * Displays a progress bar
2538
     * completed so far.
2539
     *
2540
     * @param int    $percentage Progress value to display
2541
     * @param string $text_add   Text to display near the progress value
2542
     *
2543
     * @return string HTML string containing the progress bar
2544
     */
2545
    public static function get_progress_bar($percentage = -1, $text_add = '')
2546
    {
2547
        $text = $percentage.$text_add;
2548
        $output = '<div class="progress">
2549
            <div id="progress_bar_value" 
2550
                class="progress-bar progress-bar-success" role="progressbar" 
2551
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2552
            '.$text.'
2553
            </div>
2554
        </div>';
2555
2556
        return $output;
2557
    }
2558
2559
    /**
2560
     * @param string $mode can be '%' or 'abs'
2561
     *                     otherwise this value will be used $this->progress_bar_mode
2562
     *
2563
     * @return string
2564
     */
2565
    public function getProgressBar($mode = null)
2566
    {
2567
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2568
2569
        return self::get_progress_bar($percentage, $text_add);
2570
    }
2571
2572
    /**
2573
     * Gets the progress bar info to display inside the progress bar.
2574
     * Also used by scorm_api.php.
2575
     *
2576
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2577
     *                     we display a number of completed elements per total elements
2578
     * @param int    $add  Additional steps to fake as completed
2579
     *
2580
     * @return array Percentage or number and symbol (% or /xx)
2581
     */
2582
    public function get_progress_bar_text($mode = '', $add = 0)
2583
    {
2584
        if ($this->debug > 0) {
2585
            error_log('In learnpath::get_progress_bar_text()', 0);
2586
        }
2587
        if (empty($mode)) {
2588
            $mode = $this->progress_bar_mode;
2589
        }
2590
        $total_items = $this->getTotalItemsCountWithoutDirs();
2591
        if ($this->debug > 2) {
2592
            error_log('Total items available in this learnpath: '.$total_items, 0);
2593
        }
2594
        $completeItems = $this->get_complete_items_count();
2595
        if ($this->debug > 2) {
2596
            error_log('Items completed so far: '.$completeItems, 0);
2597
        }
2598
        if ($add != 0) {
2599
            $completeItems += $add;
2600
            if ($this->debug > 2) {
2601
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2602
            }
2603
        }
2604
        $text = '';
2605
        if ($completeItems > $total_items) {
2606
            $completeItems = $total_items;
2607
        }
2608
        $percentage = 0;
2609
        if ($mode == '%') {
2610
            if ($total_items > 0) {
2611
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2612
            } else {
2613
                $percentage = 0;
2614
            }
2615
            $percentage = number_format($percentage, 0);
2616
            $text = '%';
2617
        } elseif ($mode == 'abs') {
2618
            $percentage = $completeItems;
2619
            $text = '/'.$total_items;
2620
        }
2621
2622
        return [
2623
            $percentage,
2624
            $text,
2625
        ];
2626
    }
2627
2628
    /**
2629
     * Gets the progress bar mode.
2630
     *
2631
     * @return string The progress bar mode attribute
2632
     */
2633
    public function get_progress_bar_mode()
2634
    {
2635
        if ($this->debug > 0) {
2636
            error_log('In learnpath::get_progress_bar_mode()', 0);
2637
        }
2638
        if (!empty($this->progress_bar_mode)) {
2639
            return $this->progress_bar_mode;
2640
        } else {
2641
            return '%';
2642
        }
2643
    }
2644
2645
    /**
2646
     * Gets the learnpath theme (remote or local).
2647
     *
2648
     * @return string Learnpath theme
2649
     */
2650
    public function get_theme()
2651
    {
2652
        if ($this->debug > 0) {
2653
            error_log('In learnpath::get_theme()', 0);
2654
        }
2655
        if (!empty($this->theme)) {
2656
            return $this->theme;
2657
        } else {
2658
            return '';
2659
        }
2660
    }
2661
2662
    /**
2663
     * Gets the learnpath session id.
2664
     *
2665
     * @return int
2666
     */
2667
    public function get_lp_session_id()
2668
    {
2669
        if ($this->debug > 0) {
2670
            error_log('In learnpath::get_lp_session_id()', 0);
2671
        }
2672
        if (!empty($this->lp_session_id)) {
2673
            return (int) $this->lp_session_id;
2674
        } else {
2675
            return 0;
2676
        }
2677
    }
2678
2679
    /**
2680
     * Gets the learnpath image.
2681
     *
2682
     * @return string Web URL of the LP image
2683
     */
2684
    public function get_preview_image()
2685
    {
2686
        if ($this->debug > 0) {
2687
            error_log('In learnpath::get_preview_image()', 0);
2688
        }
2689
        if (!empty($this->preview_image)) {
2690
            return $this->preview_image;
2691
        } else {
2692
            return '';
2693
        }
2694
    }
2695
2696
    /**
2697
     * @param string $size
2698
     * @param string $path_type
2699
     *
2700
     * @return bool|string
2701
     */
2702
    public function get_preview_image_path($size = null, $path_type = 'web')
2703
    {
2704
        $preview_image = $this->get_preview_image();
2705
        if (isset($preview_image) && !empty($preview_image)) {
2706
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2707
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2708
2709
            if (isset($size)) {
2710
                $info = pathinfo($preview_image);
2711
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2712
2713
                if (file_exists($image_sys_path.$image_custom_size)) {
2714
                    if ($path_type == 'web') {
2715
                        return $image_path.$image_custom_size;
2716
                    } else {
2717
                        return $image_sys_path.$image_custom_size;
2718
                    }
2719
                }
2720
            } else {
2721
                if ($path_type == 'web') {
2722
                    return $image_path.$preview_image;
2723
                } else {
2724
                    return $image_sys_path.$preview_image;
2725
                }
2726
            }
2727
        }
2728
2729
        return false;
2730
    }
2731
2732
    /**
2733
     * Gets the learnpath author.
2734
     *
2735
     * @return string LP's author
2736
     */
2737
    public function get_author()
2738
    {
2739
        if ($this->debug > 0) {
2740
            error_log('In learnpath::get_author()', 0);
2741
        }
2742
        if (!empty($this->author)) {
2743
            return $this->author;
2744
        } else {
2745
            return '';
2746
        }
2747
    }
2748
2749
    /**
2750
     * Gets hide table of contents.
2751
     *
2752
     * @return int
2753
     */
2754
    public function getHideTableOfContents()
2755
    {
2756
        return (int) $this->hide_toc_frame;
2757
    }
2758
2759
    /**
2760
     * Generate a new prerequisites string for a given item. If this item was a sco and
2761
     * its prerequisites were strings (instead of IDs), then transform those strings into
2762
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2763
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2764
     * same rule as the scormExport() method.
2765
     *
2766
     * @param int $item_id Item ID
2767
     *
2768
     * @return string Prerequisites string ready for the export as SCORM
2769
     */
2770
    public function get_scorm_prereq_string($item_id)
2771
    {
2772
        if ($this->debug > 0) {
2773
            error_log('In learnpath::get_scorm_prereq_string()');
2774
        }
2775
        if (!is_object($this->items[$item_id])) {
2776
            return false;
2777
        }
2778
        /** @var learnpathItem $oItem */
2779
        $oItem = $this->items[$item_id];
2780
        $prereq = $oItem->get_prereq_string();
2781
2782
        if (empty($prereq)) {
2783
            return '';
2784
        }
2785
        if (preg_match('/^\d+$/', $prereq) &&
2786
            isset($this->items[$prereq]) &&
2787
            is_object($this->items[$prereq])
2788
        ) {
2789
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2790
            // then simply return it (with the ITEM_ prefix).
2791
            //return 'ITEM_' . $prereq;
2792
            return $this->items[$prereq]->ref;
2793
        } else {
2794
            if (isset($this->refs_list[$prereq])) {
2795
                // It's a simple string item from which the ID can be found in the refs list,
2796
                // so we can transform it directly to an ID for export.
2797
                return $this->items[$this->refs_list[$prereq]]->ref;
2798
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2799
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2800
            } else {
2801
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2802
                // and replace them, one by one, by the internal IDs (chamilo db)
2803
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2804
                // by a space as well.
2805
                $find = [
2806
                    '&',
2807
                    '|',
2808
                    '~',
2809
                    '=',
2810
                    '<>',
2811
                    '{',
2812
                    '}',
2813
                    '*',
2814
                    '(',
2815
                    ')',
2816
                ];
2817
                $replace = [
2818
                    ' ',
2819
                    ' ',
2820
                    ' ',
2821
                    ' ',
2822
                    ' ',
2823
                    ' ',
2824
                    ' ',
2825
                    ' ',
2826
                    ' ',
2827
                    ' ',
2828
                ];
2829
                $prereq_mod = str_replace($find, $replace, $prereq);
2830
                $ids = explode(' ', $prereq_mod);
2831
                foreach ($ids as $id) {
2832
                    $id = trim($id);
2833
                    if (isset($this->refs_list[$id])) {
2834
                        $prereq = preg_replace(
2835
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2836
                            'ITEM_'.$this->refs_list[$id],
2837
                            $prereq
2838
                        );
2839
                    }
2840
                }
2841
2842
                return $prereq;
2843
            }
2844
        }
2845
    }
2846
2847
    /**
2848
     * Returns the XML DOM document's node.
2849
     *
2850
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2851
     * @param string   $id       The identifier to look for
2852
     *
2853
     * @return mixed The reference to the element found with that identifier. False if not found
2854
     */
2855
    public function get_scorm_xml_node(&$children, $id)
2856
    {
2857
        for ($i = 0; $i < $children->length; $i++) {
2858
            $item_temp = $children->item($i);
2859
            if ($item_temp->nodeName == 'item') {
2860
                if ($item_temp->getAttribute('identifier') == $id) {
2861
                    return $item_temp;
2862
                }
2863
            }
2864
            $subchildren = $item_temp->childNodes;
2865
            if ($subchildren && $subchildren->length > 0) {
2866
                $val = $this->get_scorm_xml_node($subchildren, $id);
2867
                if (is_object($val)) {
2868
                    return $val;
2869
                }
2870
            }
2871
        }
2872
2873
        return false;
2874
    }
2875
2876
    /**
2877
     * Gets the status list for all LP's items.
2878
     *
2879
     * @return array Array of [index] => [item ID => current status]
2880
     */
2881
    public function get_items_status_list()
2882
    {
2883
        if ($this->debug > 0) {
2884
            error_log('In learnpath::get_items_status_list()', 0);
2885
        }
2886
        $list = [];
2887
        foreach ($this->ordered_items as $item_id) {
2888
            $list[] = [
2889
                $item_id => $this->items[$item_id]->get_status(),
2890
            ];
2891
        }
2892
2893
        return $list;
2894
    }
2895
2896
    /**
2897
     * Return the number of interactions for the given learnpath Item View ID.
2898
     * This method can be used as static.
2899
     *
2900
     * @param int $lp_iv_id  Item View ID
2901
     * @param int $course_id course id
2902
     *
2903
     * @return int
2904
     */
2905
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2906
    {
2907
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2908
        $lp_iv_id = (int) $lp_iv_id;
2909
        $course_id = (int) $course_id;
2910
2911
        $sql = "SELECT count(*) FROM $table
2912
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2913
        $res = Database::query($sql);
2914
        $num = 0;
2915
        if (Database::num_rows($res)) {
2916
            $row = Database::fetch_array($res);
2917
            $num = $row[0];
2918
        }
2919
2920
        return $num;
2921
    }
2922
2923
    /**
2924
     * Return the interactions as an array for the given lp_iv_id.
2925
     * This method can be used as static.
2926
     *
2927
     * @param int $lp_iv_id Learnpath Item View ID
2928
     *
2929
     * @return array
2930
     *
2931
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2932
     */
2933
    public static function get_iv_interactions_array($lp_iv_id)
2934
    {
2935
        $course_id = api_get_course_int_id();
2936
        $list = [];
2937
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2938
2939
        if (empty($lp_iv_id)) {
2940
            return [];
2941
        }
2942
2943
        $sql = "SELECT * FROM $table
2944
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2945
                ORDER BY order_id ASC";
2946
        $res = Database::query($sql);
2947
        $num = Database::num_rows($res);
2948
        if ($num > 0) {
2949
            $list[] = [
2950
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2951
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2952
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2953
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2954
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2955
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2956
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2957
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2958
            ];
2959
            while ($row = Database::fetch_array($res)) {
2960
                $list[] = [
2961
                    'order_id' => ($row['order_id'] + 1),
2962
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2963
                    'type' => $row['interaction_type'],
2964
                    'time' => $row['completion_time'],
2965
                    //'correct_responses' => $row['correct_responses'],
2966
                    'correct_responses' => '', // Hide correct responses from students.
2967
                    'student_response' => $row['student_response'],
2968
                    'result' => $row['result'],
2969
                    'latency' => $row['latency'],
2970
                ];
2971
            }
2972
        }
2973
2974
        return $list;
2975
    }
2976
2977
    /**
2978
     * Return the number of objectives for the given learnpath Item View ID.
2979
     * This method can be used as static.
2980
     *
2981
     * @param int $lp_iv_id  Item View ID
2982
     * @param int $course_id Course ID
2983
     *
2984
     * @return int Number of objectives
2985
     */
2986
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2987
    {
2988
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2989
        $course_id = (int) $course_id;
2990
        $lp_iv_id = (int) $lp_iv_id;
2991
        $sql = "SELECT count(*) FROM $table
2992
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2993
        //@todo seems that this always returns 0
2994
        $res = Database::query($sql);
2995
        $num = 0;
2996
        if (Database::num_rows($res)) {
2997
            $row = Database::fetch_array($res);
2998
            $num = $row[0];
2999
        }
3000
3001
        return $num;
3002
    }
3003
3004
    /**
3005
     * Return the objectives as an array for the given lp_iv_id.
3006
     * This method can be used as static.
3007
     *
3008
     * @param int $lpItemViewId Learnpath Item View ID
3009
     *
3010
     * @return array
3011
     *
3012
     * @todo    Translate labels
3013
     */
3014
    public static function get_iv_objectives_array($lpItemViewId = 0)
3015
    {
3016
        $course_id = api_get_course_int_id();
3017
        $lpItemViewId = (int) $lpItemViewId;
3018
3019
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3020
        $sql = "SELECT * FROM $table
3021
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3022
                ORDER BY order_id ASC";
3023
        $res = Database::query($sql);
3024
        $num = Database::num_rows($res);
3025
        $list = [];
3026
        if ($num > 0) {
3027
            $list[] = [
3028
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3029
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3030
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3031
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3032
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3033
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3034
            ];
3035
            while ($row = Database::fetch_array($res)) {
3036
                $list[] = [
3037
                    'order_id' => ($row['order_id'] + 1),
3038
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3039
                    'score_raw' => $row['score_raw'],
3040
                    'score_max' => $row['score_max'],
3041
                    'score_min' => $row['score_min'],
3042
                    'status' => $row['status'],
3043
                ];
3044
            }
3045
        }
3046
3047
        return $list;
3048
    }
3049
3050
    /**
3051
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3052
     * used by get_html_toc() to be ready to display.
3053
     *
3054
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3055
     */
3056
    public function get_toc()
3057
    {
3058
        if ($this->debug > 0) {
3059
            error_log('learnpath::get_toc()', 0);
3060
        }
3061
        $toc = [];
3062
        foreach ($this->ordered_items as $item_id) {
3063
            if ($this->debug > 2) {
3064
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3065
            }
3066
            // TODO: Change this link generation and use new function instead.
3067
            $toc[] = [
3068
                'id' => $item_id,
3069
                'title' => $this->items[$item_id]->get_title(),
3070
                'status' => $this->items[$item_id]->get_status(),
3071
                'level' => $this->items[$item_id]->get_level(),
3072
                'type' => $this->items[$item_id]->get_type(),
3073
                'description' => $this->items[$item_id]->get_description(),
3074
                'path' => $this->items[$item_id]->get_path(),
3075
                'parent' => $this->items[$item_id]->get_parent(),
3076
            ];
3077
        }
3078
        if ($this->debug > 2) {
3079
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3080
        }
3081
3082
        return $toc;
3083
    }
3084
3085
    /**
3086
     * Generate and return the table of contents for this learnpath. The JS
3087
     * table returned is used inside of scorm_api.php.
3088
     *
3089
     * @param string $varname
3090
     *
3091
     * @return string A JS array variable construction
3092
     */
3093
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3094
    {
3095
        if ($this->debug > 0) {
3096
            error_log('In learnpath::get_items_details_as_js()', 0);
3097
        }
3098
        $toc = $varname.' = new Array();';
3099
        foreach ($this->ordered_items as $item_id) {
3100
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3101
        }
3102
        if ($this->debug > 2) {
3103
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3104
        }
3105
3106
        return $toc;
3107
    }
3108
3109
    /**
3110
     * Gets the learning path type.
3111
     *
3112
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3113
     *
3114
     * @return mixed Type ID or name, depending on the parameter
3115
     */
3116
    public function get_type($get_name = false)
3117
    {
3118
        $res = false;
3119
        if ($this->debug > 0) {
3120
            error_log('In learnpath::get_type()', 0);
3121
        }
3122
        if (!empty($this->type) && (!$get_name)) {
3123
            $res = $this->type;
3124
        }
3125
        if ($this->debug > 2) {
3126
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3127
        }
3128
3129
        return $res;
3130
    }
3131
3132
    /**
3133
     * Gets the learning path type as static method.
3134
     *
3135
     * @param int $lp_id
3136
     *
3137
     * @return mixed Type ID or name, depending on the parameter
3138
     */
3139
    public static function get_type_static($lp_id = 0)
3140
    {
3141
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3142
        $lp_id = (int) $lp_id;
3143
        $sql = "SELECT lp_type FROM $tbl_lp
3144
                WHERE iid = $lp_id";
3145
        $res = Database::query($sql);
3146
        if ($res === false) {
3147
            return null;
3148
        }
3149
        if (Database::num_rows($res) <= 0) {
3150
            return null;
3151
        }
3152
        $row = Database::fetch_array($res);
3153
3154
        return $row['lp_type'];
3155
    }
3156
3157
    /**
3158
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3159
     * This method can be used as abstract and is recursive.
3160
     *
3161
     * @param int $lp        Learnpath ID
3162
     * @param int $parent    Parent ID of the items to look for
3163
     * @param int $course_id
3164
     *
3165
     * @return array Ordered list of item IDs (empty array on error)
3166
     */
3167
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3168
    {
3169
        if (empty($course_id)) {
3170
            $course_id = api_get_course_int_id();
3171
        } else {
3172
            $course_id = (int) $course_id;
3173
        }
3174
        $list = [];
3175
3176
        if (empty($lp)) {
3177
            return $list;
3178
        }
3179
3180
        $lp = (int) $lp;
3181
        $parent = (int) $parent;
3182
3183
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3184
        $sql = "SELECT iid FROM $tbl_lp_item
3185
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3186
                ORDER BY display_order";
3187
3188
        $res = Database::query($sql);
3189
        while ($row = Database::fetch_array($res)) {
3190
            $sublist = self::get_flat_ordered_items_list(
3191
                $lp,
3192
                $row['iid'],
3193
                $course_id
3194
            );
3195
            $list[] = $row['iid'];
3196
            foreach ($sublist as $item) {
3197
                $list[] = $item;
3198
            }
3199
        }
3200
3201
        return $list;
3202
    }
3203
3204
    /**
3205
     * @return array
3206
     */
3207
    public static function getChapterTypes()
3208
    {
3209
        return [
3210
            'dir',
3211
        ];
3212
    }
3213
3214
    /**
3215
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3216
     *
3217
     * @param $tree
3218
     *
3219
     * @return array HTML TOC ready to display
3220
     */
3221
    public function getParentToc($tree)
3222
    {
3223
        if ($this->debug > 0) {
3224
            error_log('In learnpath::get_html_toc()', 0);
3225
        }
3226
        if (empty($tree)) {
3227
            $tree = $this->get_toc();
3228
        }
3229
        $dirTypes = self::getChapterTypes();
3230
        $myCurrentId = $this->get_current_item_id();
3231
        $listParent = [];
3232
        $listChildren = [];
3233
        $listNotParent = [];
3234
        $list = [];
3235
        foreach ($tree as $subtree) {
3236
            if (in_array($subtree['type'], $dirTypes)) {
3237
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3238
                $subtree['children'] = $listChildren;
3239
                if (!empty($subtree['children'])) {
3240
                    foreach ($subtree['children'] as $subItem) {
3241
                        if ($subItem['id'] == $this->current) {
3242
                            $subtree['parent_current'] = 'in';
3243
                            $subtree['current'] = 'on';
3244
                        }
3245
                    }
3246
                }
3247
                $listParent[] = $subtree;
3248
            }
3249
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3250
                $classStatus = [
3251
                    'not attempted' => 'scorm_not_attempted',
3252
                    'incomplete' => 'scorm_not_attempted',
3253
                    'failed' => 'scorm_failed',
3254
                    'completed' => 'scorm_completed',
3255
                    'passed' => 'scorm_completed',
3256
                    'succeeded' => 'scorm_completed',
3257
                    'browsed' => 'scorm_completed',
3258
                ];
3259
3260
                if (isset($classStatus[$subtree['status']])) {
3261
                    $cssStatus = $classStatus[$subtree['status']];
3262
                }
3263
3264
                $title = Security::remove_XSS($subtree['title']);
3265
                unset($subtree['title']);
3266
3267
                if (empty($title)) {
3268
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3269
                }
3270
                $classStyle = null;
3271
                if ($subtree['id'] == $this->current) {
3272
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3273
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3274
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3275
                }
3276
                $subtree['title'] = $title;
3277
                $subtree['class'] = $classStyle.' '.$cssStatus;
3278
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3279
                $subtree['current_id'] = $myCurrentId;
3280
                $listNotParent[] = $subtree;
3281
            }
3282
        }
3283
3284
        $list['are_parents'] = $listParent;
3285
        $list['not_parents'] = $listNotParent;
3286
3287
        return $list;
3288
    }
3289
3290
    /**
3291
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3292
     *
3293
     * @param array $tree
3294
     * @param int   $id
3295
     * @param bool  $parent
3296
     *
3297
     * @return array HTML TOC ready to display
3298
     */
3299
    public function getChildrenToc($tree, $id, $parent = true)
3300
    {
3301
        if ($this->debug > 0) {
3302
            error_log('In learnpath::get_html_toc()', 0);
3303
        }
3304
        if (empty($tree)) {
3305
            $tree = $this->get_toc();
3306
        }
3307
3308
        $dirTypes = self::getChapterTypes();
3309
        $mycurrentitemid = $this->get_current_item_id();
3310
        $list = [];
3311
        $classStatus = [
3312
            'not attempted' => 'scorm_not_attempted',
3313
            'incomplete' => 'scorm_not_attempted',
3314
            'failed' => 'scorm_failed',
3315
            'completed' => 'scorm_completed',
3316
            'passed' => 'scorm_completed',
3317
            'succeeded' => 'scorm_completed',
3318
            'browsed' => 'scorm_completed',
3319
        ];
3320
3321
        foreach ($tree as $subtree) {
3322
            $subtree['tree'] = null;
3323
3324
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3325
                if ($subtree['id'] == $this->current) {
3326
                    $subtree['current'] = 'active';
3327
                } else {
3328
                    $subtree['current'] = null;
3329
                }
3330
                if (isset($classStatus[$subtree['status']])) {
3331
                    $cssStatus = $classStatus[$subtree['status']];
3332
                }
3333
3334
                $title = Security::remove_XSS($subtree['title']);
3335
                unset($subtree['title']);
3336
                if (empty($title)) {
3337
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3338
                }
3339
3340
                $classStyle = null;
3341
                if ($subtree['id'] == $this->current) {
3342
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3343
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3344
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3345
                }
3346
3347
                if (in_array($subtree['type'], $dirTypes)) {
3348
                    $subtree['title'] = stripslashes($title);
3349
                } else {
3350
                    $subtree['title'] = $title;
3351
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3352
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3353
                    $subtree['current_id'] = $mycurrentitemid;
3354
                }
3355
                $list[] = $subtree;
3356
            }
3357
        }
3358
3359
        return $list;
3360
    }
3361
3362
    /**
3363
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3364
     *
3365
     * @param array $toc_list
3366
     *
3367
     * @return array HTML TOC ready to display
3368
     */
3369
    public function getListArrayToc($toc_list = [])
3370
    {
3371
        if ($this->debug > 0) {
3372
            error_log('In learnpath::get_html_toc()', 0);
3373
        }
3374
        if (empty($toc_list)) {
3375
            $toc_list = $this->get_toc();
3376
        }
3377
        // Temporary variables.
3378
        $mycurrentitemid = $this->get_current_item_id();
3379
        $list = [];
3380
        $arrayList = [];
3381
        $classStatus = [
3382
            'not attempted' => 'scorm_not_attempted',
3383
            'incomplete' => 'scorm_not_attempted',
3384
            'failed' => 'scorm_failed',
3385
            'completed' => 'scorm_completed',
3386
            'passed' => 'scorm_completed',
3387
            'succeeded' => 'scorm_completed',
3388
            'browsed' => 'scorm_completed',
3389
        ];
3390
3391
        foreach ($toc_list as $item) {
3392
            $list['id'] = $item['id'];
3393
            $list['status'] = $item['status'];
3394
            $cssStatus = null;
3395
3396
            if (isset($classStatus[$item['status']])) {
3397
                $cssStatus = $classStatus[$item['status']];
3398
            }
3399
3400
            $classStyle = ' ';
3401
            $dirTypes = self::getChapterTypes();
3402
3403
            if (in_array($item['type'], $dirTypes)) {
3404
                $classStyle = 'scorm_item_section ';
3405
            }
3406
            if ($item['id'] == $this->current) {
3407
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3408
            } elseif (!in_array($item['type'], $dirTypes)) {
3409
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3410
            }
3411
            $title = $item['title'];
3412
            if (empty($title)) {
3413
                $title = self::rl_get_resource_name(
3414
                    api_get_course_id(),
3415
                    $this->get_id(),
3416
                    $item['id']
3417
                );
3418
            }
3419
            $title = Security::remove_XSS($item['title']);
3420
3421
            if (empty($item['description'])) {
3422
                $list['description'] = $title;
3423
            } else {
3424
                $list['description'] = $item['description'];
3425
            }
3426
3427
            $list['class'] = $classStyle.' '.$cssStatus;
3428
            $list['level'] = $item['level'];
3429
            $list['type'] = $item['type'];
3430
3431
            if (in_array($item['type'], $dirTypes)) {
3432
                $list['css_level'] = 'level_'.$item['level'];
3433
            } else {
3434
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3435
            }
3436
3437
            if (in_array($item['type'], $dirTypes)) {
3438
                $list['title'] = stripslashes($title);
3439
            } else {
3440
                $list['title'] = stripslashes($title);
3441
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3442
                $list['current_id'] = $mycurrentitemid;
3443
            }
3444
            $arrayList[] = $list;
3445
        }
3446
3447
        return $arrayList;
3448
    }
3449
3450
    /**
3451
     * Returns an HTML-formatted string ready to display with teacher buttons
3452
     * in LP view menu.
3453
     *
3454
     * @return string HTML TOC ready to display
3455
     */
3456
    public function get_teacher_toc_buttons()
3457
    {
3458
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3459
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3460
        $html = '';
3461
        if ($isAllow && $hideIcons == false) {
3462
            if ($this->get_lp_session_id() == api_get_session_id()) {
3463
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3464
                $html .= '<div class="btn-group">';
3465
                $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'>".
3466
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3467
                $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'>".
3468
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3469
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3470
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3471
                $html .= '</div>';
3472
                $html .= '</div>';
3473
            }
3474
        }
3475
3476
        return $html;
3477
    }
3478
3479
    /**
3480
     * Gets the learnpath maker name - generally the editor's name.
3481
     *
3482
     * @return string Learnpath maker name
3483
     */
3484
    public function get_maker()
3485
    {
3486
        if ($this->debug > 0) {
3487
            error_log('In learnpath::get_maker()', 0);
3488
        }
3489
        if (!empty($this->maker)) {
3490
            return $this->maker;
3491
        } else {
3492
            return '';
3493
        }
3494
    }
3495
3496
    /**
3497
     * Gets the learnpath name/title.
3498
     *
3499
     * @return string Learnpath name/title
3500
     */
3501
    public function get_name()
3502
    {
3503
        if ($this->debug > 0) {
3504
            error_log('In learnpath::get_name()', 0);
3505
        }
3506
        if (!empty($this->name)) {
3507
            return $this->name;
3508
        } else {
3509
            return 'N/A';
3510
        }
3511
    }
3512
3513
    /**
3514
     * Gets a link to the resource from the present location, depending on item ID.
3515
     *
3516
     * @param string $type         Type of link expected
3517
     * @param int    $item_id      Learnpath item ID
3518
     * @param bool   $provided_toc
3519
     *
3520
     * @return string $provided_toc Link to the lp_item resource
3521
     */
3522
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3523
    {
3524
        $course_id = $this->get_course_int_id();
3525
        if ($this->debug > 0) {
3526
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3527
        }
3528
        if (empty($item_id)) {
3529
            if ($this->debug > 2) {
3530
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3531
                error_log('using current: '.$this->get_current_item_id(), 0);
3532
            }
3533
            $item_id = $this->get_current_item_id();
3534
3535
            if (empty($item_id)) {
3536
                if ($this->debug > 2) {
3537
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3538
                }
3539
                //still empty, this means there was no item_id given and we are not in an object context or
3540
                //the object property is empty, return empty link
3541
                $this->first();
3542
3543
                return '';
3544
            }
3545
        }
3546
3547
        $file = '';
3548
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3549
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3550
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3551
        $item_id = (int) $item_id;
3552
3553
        $sql = "SELECT
3554
                    l.lp_type as ltype,
3555
                    l.path as lpath,
3556
                    li.item_type as litype,
3557
                    li.path as lipath,
3558
                    li.parameters as liparams
3559
        		FROM $lp_table l
3560
                INNER JOIN $lp_item_table li
3561
                ON (li.lp_id = l.iid)
3562
        		WHERE 
3563
        		    li.iid = $item_id 
3564
        		";
3565
        if ($this->debug > 2) {
3566
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3567
        }
3568
        $res = Database::query($sql);
3569
        if (Database::num_rows($res) > 0) {
3570
            $row = Database::fetch_array($res);
3571
            $lp_type = $row['ltype'];
3572
            $lp_path = $row['lpath'];
3573
            $lp_item_type = $row['litype'];
3574
            $lp_item_path = $row['lipath'];
3575
            $lp_item_params = $row['liparams'];
3576
3577
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3578
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3579
            }
3580
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3581
            if ($type === 'http') {
3582
                //web path
3583
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3584
            } else {
3585
                $course_path = $sys_course_path; //system path
3586
            }
3587
3588
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3589
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3590
            if (in_array(
3591
                $lp_item_type,
3592
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3593
            )
3594
            ) {
3595
                $lp_type = 1;
3596
            }
3597
3598
            if ($this->debug > 2) {
3599
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3600
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3601
            }
3602
3603
            // Now go through the specific cases to get the end of the path
3604
            // @todo Use constants instead of int values.
3605
            switch ($lp_type) {
3606
                case 1:
3607
                    $file = self::rl_get_resource_link_for_learnpath(
3608
                        $course_id,
3609
                        $this->get_id(),
3610
                        $item_id,
3611
                        $this->get_view_id()
3612
                    );
3613
3614
                    if ($this->debug > 0) {
3615
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3616
                    }
3617
3618
                    switch ($lp_item_type) {
3619
                        case 'document':
3620
                            // Shows a button to download the file instead of just downloading the file directly.
3621
                            $documentPathInfo = pathinfo($file);
3622
                            if (isset($documentPathInfo['extension'])) {
3623
                                $parsed = parse_url($documentPathInfo['extension']);
3624
                                if (isset($parsed['path'])) {
3625
                                    $extension = $parsed['path'];
3626
                                    $extensionsToDownload = ['zip', 'ppt', 'pptx', 'ods', 'xlsx', 'xls', 'csv'];
3627
3628
                                    if (in_array($extension, $extensionsToDownload)) {
3629
                                        $file = api_get_path(WEB_CODE_PATH).
3630
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3631
                                    }
3632
                                }
3633
                            }
3634
                            break;
3635
                        case 'dir':
3636
                            $file = 'lp_content.php?type=dir';
3637
                            break;
3638
                        case 'link':
3639
                            if (Link::is_youtube_link($file)) {
3640
                                $src = Link::get_youtube_video_id($file);
3641
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3642
                            } elseif (Link::isVimeoLink($file)) {
3643
                                $src = Link::getVimeoLinkId($file);
3644
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3645
                            } else {
3646
                                // If the current site is HTTPS and the link is
3647
                                // HTTP, browsers will refuse opening the link
3648
                                $urlId = api_get_current_access_url_id();
3649
                                $url = api_get_access_url($urlId, false);
3650
                                $protocol = substr($url['url'], 0, 5);
3651
                                if ($protocol === 'https') {
3652
                                    $linkProtocol = substr($file, 0, 5);
3653
                                    if ($linkProtocol === 'http:') {
3654
                                        //this is the special intervention case
3655
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3656
                                    }
3657
                                }
3658
                            }
3659
                            break;
3660
                        case 'quiz':
3661
                            // Check how much attempts of a exercise exits in lp
3662
                            $lp_item_id = $this->get_current_item_id();
3663
                            $lp_view_id = $this->get_view_id();
3664
3665
                            $prevent_reinit = null;
3666
                            if (isset($this->items[$this->current])) {
3667
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3668
                            }
3669
3670
                            if (empty($provided_toc)) {
3671
                                if ($this->debug > 0) {
3672
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3673
                                }
3674
                                $list = $this->get_toc();
3675
                            } else {
3676
                                if ($this->debug > 0) {
3677
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3678
                                }
3679
                                $list = $provided_toc;
3680
                            }
3681
3682
                            $type_quiz = false;
3683
                            foreach ($list as $toc) {
3684
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3685
                                    $type_quiz = true;
3686
                                }
3687
                            }
3688
3689
                            if ($type_quiz) {
3690
                                $lp_item_id = (int) $lp_item_id;
3691
                                $lp_view_id = (int) $lp_view_id;
3692
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3693
                                        WHERE
3694
                                            c_id = $course_id AND
3695
                                            lp_item_id='".$lp_item_id."' AND
3696
                                            lp_view_id ='".$lp_view_id."' AND
3697
                                            status='completed'";
3698
                                $result = Database::query($sql);
3699
                                $row_count = Database:: fetch_row($result);
3700
                                $count_item_view = (int) $row_count[0];
3701
                                $not_multiple_attempt = 0;
3702
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3703
                                    $not_multiple_attempt = 1;
3704
                                }
3705
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3706
                            }
3707
                            break;
3708
                    }
3709
3710
                    $tmp_array = explode('/', $file);
3711
                    $document_name = $tmp_array[count($tmp_array) - 1];
3712
                    if (strpos($document_name, '_DELETED_')) {
3713
                        $file = 'blank.php?error=document_deleted';
3714
                    }
3715
                    break;
3716
                case 2:
3717
                    if ($this->debug > 2) {
3718
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3719
                    }
3720
3721
                    if ($lp_item_type != 'dir') {
3722
                        // Quite complex here:
3723
                        // We want to make sure 'http://' (and similar) links can
3724
                        // be loaded as is (withouth the Chamilo path in front) but
3725
                        // some contents use this form: resource.htm?resource=http://blablabla
3726
                        // which means we have to find a protocol at the path's start, otherwise
3727
                        // it should not be considered as an external URL.
3728
                        // if ($this->prerequisites_match($item_id)) {
3729
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3730
                            if ($this->debug > 2) {
3731
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3732
                            }
3733
                            // Distant url, return as is.
3734
                            $file = $lp_item_path;
3735
                        } else {
3736
                            if ($this->debug > 2) {
3737
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3738
                            }
3739
                            // Prevent getting untranslatable urls.
3740
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3741
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3742
                            // Prepare the path.
3743
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3744
                            // TODO: Fix this for urls with protocol header.
3745
                            $file = str_replace('//', '/', $file);
3746
                            $file = str_replace(':/', '://', $file);
3747
                            if (substr($lp_path, -1) == '/') {
3748
                                $lp_path = substr($lp_path, 0, -1);
3749
                            }
3750
3751
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3752
                                // if file not found.
3753
                                $decoded = html_entity_decode($lp_item_path);
3754
                                list($decoded) = explode('?', $decoded);
3755
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3756
                                    $file = self::rl_get_resource_link_for_learnpath(
3757
                                        $course_id,
3758
                                        $this->get_id(),
3759
                                        $item_id,
3760
                                        $this->get_view_id()
3761
                                    );
3762
                                    if (empty($file)) {
3763
                                        $file = 'blank.php?error=document_not_found';
3764
                                    } else {
3765
                                        $tmp_array = explode('/', $file);
3766
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3767
                                        if (strpos($document_name, '_DELETED_')) {
3768
                                            $file = 'blank.php?error=document_deleted';
3769
                                        } else {
3770
                                            $file = 'blank.php?error=document_not_found';
3771
                                        }
3772
                                    }
3773
                                } else {
3774
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3775
                                }
3776
                            }
3777
                        }
3778
3779
                        // We want to use parameters if they were defined in the imsmanifest
3780
                        if (strpos($file, 'blank.php') === false) {
3781
                            $lp_item_params = ltrim($lp_item_params, '?');
3782
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3783
                        }
3784
                    } else {
3785
                        $file = 'lp_content.php?type=dir';
3786
                    }
3787
                    break;
3788
                case 3:
3789
                    if ($this->debug > 2) {
3790
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3791
                    }
3792
                    // Formatting AICC HACP append URL.
3793
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3794
                    if (!empty($lp_item_params)) {
3795
                        $aicc_append .= $lp_item_params.'&';
3796
                    }
3797
                    if ($lp_item_type != 'dir') {
3798
                        // Quite complex here:
3799
                        // We want to make sure 'http://' (and similar) links can
3800
                        // be loaded as is (withouth the Chamilo path in front) but
3801
                        // some contents use this form: resource.htm?resource=http://blablabla
3802
                        // which means we have to find a protocol at the path's start, otherwise
3803
                        // it should not be considered as an external URL.
3804
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3805
                            if ($this->debug > 2) {
3806
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3807
                            }
3808
                            // Distant url, return as is.
3809
                            $file = $lp_item_path;
3810
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3811
                            /*
3812
                            if (stristr($file,'<servername>') !== false) {
3813
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3814
                            }
3815
                            */
3816
                            if (stripos($file, '<servername>') !== false) {
3817
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3818
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3819
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3820
                            }
3821
3822
                            $file .= $aicc_append;
3823
                        } else {
3824
                            if ($this->debug > 2) {
3825
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3826
                            }
3827
                            // Prevent getting untranslatable urls.
3828
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3829
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3830
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3831
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3832
                            // TODO: Fix this for urls with protocol header.
3833
                            $file = str_replace('//', '/', $file);
3834
                            $file = str_replace(':/', '://', $file);
3835
                            $file .= $aicc_append;
3836
                        }
3837
                    } else {
3838
                        $file = 'lp_content.php?type=dir';
3839
                    }
3840
                    break;
3841
                case 4:
3842
                    break;
3843
                default:
3844
                    break;
3845
            }
3846
            // Replace &amp; by & because &amp; will break URL with params
3847
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3848
        }
3849
        if ($this->debug > 2) {
3850
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3851
        }
3852
3853
        return $file;
3854
    }
3855
3856
    /**
3857
     * Gets the latest usable view or generate a new one.
3858
     *
3859
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3860
     *
3861
     * @return int DB lp_view id
3862
     */
3863
    public function get_view($attempt_num = 0)
3864
    {
3865
        if ($this->debug > 0) {
3866
            error_log('In learnpath::get_view()', 0);
3867
        }
3868
        $search = '';
3869
        // Use $attempt_num to enable multi-views management (disabled so far).
3870
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3871
            $search = 'AND view_count = '.$attempt_num;
3872
        }
3873
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3874
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3875
3876
        $course_id = api_get_course_int_id();
3877
        $sessionId = api_get_session_id();
3878
3879
        $sql = "SELECT iid, view_count FROM $lp_view_table
3880
        		WHERE
3881
        		    c_id = $course_id AND
3882
        		    lp_id = ".$this->get_id()." AND
3883
        		    user_id = ".$this->get_user_id()." AND
3884
        		    session_id = $sessionId
3885
        		    $search
3886
                ORDER BY view_count DESC";
3887
        $res = Database::query($sql);
3888
        if (Database::num_rows($res) > 0) {
3889
            $row = Database::fetch_array($res);
3890
            $this->lp_view_id = $row['iid'];
3891
        } elseif (!api_is_invitee()) {
3892
            // There is no database record, create one.
3893
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3894
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3895
            Database::query($sql);
3896
            $id = Database::insert_id();
3897
            $this->lp_view_id = $id;
3898
3899
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3900
            Database::query($sql);
3901
        }
3902
3903
        return $this->lp_view_id;
3904
    }
3905
3906
    /**
3907
     * Gets the current view id.
3908
     *
3909
     * @return int View ID (from lp_view)
3910
     */
3911
    public function get_view_id()
3912
    {
3913
        if ($this->debug > 0) {
3914
            error_log('In learnpath::get_view_id()', 0);
3915
        }
3916
        if (!empty($this->lp_view_id)) {
3917
            return $this->lp_view_id;
3918
        } else {
3919
            return 0;
3920
        }
3921
    }
3922
3923
    /**
3924
     * Gets the update queue.
3925
     *
3926
     * @return array Array containing IDs of items to be updated by JavaScript
3927
     */
3928
    public function get_update_queue()
3929
    {
3930
        if ($this->debug > 0) {
3931
            error_log('In learnpath::get_update_queue()', 0);
3932
        }
3933
3934
        return $this->update_queue;
3935
    }
3936
3937
    /**
3938
     * Gets the user ID.
3939
     *
3940
     * @return int User ID
3941
     */
3942
    public function get_user_id()
3943
    {
3944
        if ($this->debug > 0) {
3945
            error_log('In learnpath::get_user_id()', 0);
3946
        }
3947
        if (!empty($this->user_id)) {
3948
            return $this->user_id;
3949
        } else {
3950
            return false;
3951
        }
3952
    }
3953
3954
    /**
3955
     * Checks if any of the items has an audio element attached.
3956
     *
3957
     * @return bool True or false
3958
     */
3959
    public function has_audio()
3960
    {
3961
        if ($this->debug > 1) {
3962
            error_log('In learnpath::has_audio()', 0);
3963
        }
3964
        $has = false;
3965
        foreach ($this->items as $i => $item) {
3966
            if (!empty($this->items[$i]->audio)) {
3967
                $has = true;
3968
                break;
3969
            }
3970
        }
3971
3972
        return $has;
3973
    }
3974
3975
    /**
3976
     * Moves an item up and down at its level.
3977
     *
3978
     * @param int    $id        Item to move up and down
3979
     * @param string $direction Direction 'up' or 'down'
3980
     *
3981
     * @return bool|int
3982
     */
3983
    public function move_item($id, $direction)
3984
    {
3985
        $course_id = api_get_course_int_id();
3986
        if ($this->debug > 0) {
3987
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
3988
        }
3989
        if (empty($id) || empty($direction)) {
3990
            return false;
3991
        }
3992
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3993
        $sql_sel = "SELECT *
3994
                    FROM $tbl_lp_item
3995
                    WHERE  
3996
                        iid = $id
3997
                    ";
3998
        $res_sel = Database::query($sql_sel);
3999
        // Check if elem exists.
4000
        if (Database::num_rows($res_sel) < 1) {
4001
            return false;
4002
        }
4003
        // Gather data.
4004
        $row = Database::fetch_array($res_sel);
4005
        $previous = $row['previous_item_id'];
4006
        $next = $row['next_item_id'];
4007
        $display = $row['display_order'];
4008
        $parent = $row['parent_item_id'];
4009
        $lp = $row['lp_id'];
4010
        // Update the item (switch with previous/next one).
4011
        switch ($direction) {
4012
            case 'up':
4013
                if ($this->debug > 2) {
4014
                    error_log('Movement up detected', 0);
4015
                }
4016
                if ($display > 1) {
4017
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4018
                                 WHERE iid = $previous";
4019
4020
                    if ($this->debug > 2) {
4021
                        error_log('Selecting previous: '.$sql_sel2, 0);
4022
                    }
4023
                    $res_sel2 = Database::query($sql_sel2);
4024
                    if (Database::num_rows($res_sel2) < 1) {
4025
                        $previous_previous = 0;
4026
                    }
4027
                    // Gather data.
4028
                    $row2 = Database::fetch_array($res_sel2);
4029
                    $previous_previous = $row2['previous_item_id'];
4030
                    // Update previous_previous item (switch "next" with current).
4031
                    if ($previous_previous != 0) {
4032
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4033
                                        next_item_id = $id
4034
                                    WHERE iid = $previous_previous";
4035
                        if ($this->debug > 2) {
4036
                            error_log($sql_upd2, 0);
4037
                        }
4038
                        Database::query($sql_upd2);
4039
                    }
4040
                    // Update previous item (switch with current).
4041
                    if ($previous != 0) {
4042
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4043
                                    next_item_id = $next,
4044
                                    previous_item_id = $id,
4045
                                    display_order = display_order +1
4046
                                    WHERE iid = $previous";
4047
                        if ($this->debug > 2) {
4048
                            error_log($sql_upd2, 0);
4049
                        }
4050
                        Database::query($sql_upd2);
4051
                    }
4052
4053
                    // Update current item (switch with previous).
4054
                    if ($id != 0) {
4055
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4056
                                        next_item_id = $previous,
4057
                                        previous_item_id = $previous_previous,
4058
                                        display_order = display_order-1
4059
                                    WHERE c_id = ".$course_id." AND id = $id";
4060
                        if ($this->debug > 2) {
4061
                            error_log($sql_upd2, 0);
4062
                        }
4063
                        Database::query($sql_upd2);
4064
                    }
4065
                    // Update next item (new previous item).
4066
                    if (!empty($next)) {
4067
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4068
                                     WHERE iid = $next";
4069
                        if ($this->debug > 2) {
4070
                            error_log($sql_upd2, 0);
4071
                        }
4072
                        Database::query($sql_upd2);
4073
                    }
4074
                    $display = $display - 1;
4075
                }
4076
                break;
4077
            case 'down':
4078
                if ($this->debug > 2) {
4079
                    error_log('Movement down detected', 0);
4080
                }
4081
                if ($next != 0) {
4082
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4083
                                 WHERE iid = $next";
4084
                    if ($this->debug > 2) {
4085
                        error_log('Selecting next: '.$sql_sel2, 0);
4086
                    }
4087
                    $res_sel2 = Database::query($sql_sel2);
4088
                    if (Database::num_rows($res_sel2) < 1) {
4089
                        $next_next = 0;
4090
                    }
4091
                    // Gather data.
4092
                    $row2 = Database::fetch_array($res_sel2);
4093
                    $next_next = $row2['next_item_id'];
4094
                    // Update previous item (switch with current).
4095
                    if ($previous != 0) {
4096
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4097
                                     SET next_item_id = $next
4098
                                     WHERE iid = $previous";
4099
                        Database::query($sql_upd2);
4100
                    }
4101
                    // Update current item (switch with previous).
4102
                    if ($id != 0) {
4103
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4104
                                     previous_item_id = $next, 
4105
                                     next_item_id = $next_next, 
4106
                                     display_order = display_order + 1
4107
                                     WHERE iid = $id";
4108
                        Database::query($sql_upd2);
4109
                    }
4110
4111
                    // Update next item (new previous item).
4112
                    if ($next != 0) {
4113
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4114
                                     previous_item_id = $previous, 
4115
                                     next_item_id = $id, 
4116
                                     display_order = display_order-1
4117
                                     WHERE iid = $next";
4118
                        Database::query($sql_upd2);
4119
                    }
4120
4121
                    // Update next_next item (switch "previous" with current).
4122
                    if ($next_next != 0) {
4123
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4124
                                     previous_item_id = $id
4125
                                     WHERE iid = $next_next";
4126
                        Database::query($sql_upd2);
4127
                    }
4128
                    $display = $display + 1;
4129
                }
4130
                break;
4131
            default:
4132
                return false;
4133
        }
4134
4135
        return $display;
4136
    }
4137
4138
    /**
4139
     * Move a LP up (display_order).
4140
     *
4141
     * @param int $lp_id      Learnpath ID
4142
     * @param int $categoryId Category ID
4143
     *
4144
     * @return bool
4145
     */
4146
    public static function move_up($lp_id, $categoryId = 0)
4147
    {
4148
        $courseId = api_get_course_int_id();
4149
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4150
4151
        $categoryCondition = '';
4152
        if (!empty($categoryId)) {
4153
            $categoryId = (int) $categoryId;
4154
            $categoryCondition = " AND category_id = $categoryId";
4155
        }
4156
        $sql = "SELECT * FROM $lp_table
4157
                WHERE c_id = $courseId
4158
                $categoryCondition
4159
                ORDER BY display_order";
4160
        $res = Database::query($sql);
4161
        if ($res === false) {
4162
            return false;
4163
        }
4164
4165
        $lps = [];
4166
        $lp_order = [];
4167
        $num = Database::num_rows($res);
4168
        // First check the order is correct, globally (might be wrong because
4169
        // of versions < 1.8.4)
4170
        if ($num > 0) {
4171
            $i = 1;
4172
            while ($row = Database::fetch_array($res)) {
4173
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4174
                    $sql = "UPDATE $lp_table SET display_order = $i
4175
                            WHERE iid = ".$row['iid'];
4176
                    Database::query($sql);
4177
                }
4178
                $row['display_order'] = $i;
4179
                $lps[$row['iid']] = $row;
4180
                $lp_order[$i] = $row['iid'];
4181
                $i++;
4182
            }
4183
        }
4184
        if ($num > 1) { // If there's only one element, no need to sort.
4185
            $order = $lps[$lp_id]['display_order'];
4186
            if ($order > 1) { // If it's the first element, no need to move up.
4187
                $sql = "UPDATE $lp_table SET display_order = $order
4188
                        WHERE iid = ".$lp_order[$order - 1];
4189
                Database::query($sql);
4190
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4191
                        WHERE iid = $lp_id";
4192
                Database::query($sql);
4193
            }
4194
        }
4195
4196
        return true;
4197
    }
4198
4199
    /**
4200
     * Move a learnpath down (display_order).
4201
     *
4202
     * @param int $lp_id      Learnpath ID
4203
     * @param int $categoryId Category ID
4204
     *
4205
     * @return bool
4206
     */
4207
    public static function move_down($lp_id, $categoryId = 0)
4208
    {
4209
        $courseId = api_get_course_int_id();
4210
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4211
4212
        $categoryCondition = '';
4213
        if (!empty($categoryId)) {
4214
            $categoryId = (int) $categoryId;
4215
            $categoryCondition = " AND category_id = $categoryId";
4216
        }
4217
4218
        $sql = "SELECT * FROM $lp_table
4219
                WHERE c_id = $courseId
4220
                $categoryCondition
4221
                ORDER BY display_order";
4222
        $res = Database::query($sql);
4223
        if ($res === false) {
4224
            return false;
4225
        }
4226
        $lps = [];
4227
        $lp_order = [];
4228
        $num = Database::num_rows($res);
4229
        $max = 0;
4230
        // First check the order is correct, globally (might be wrong because
4231
        // of versions < 1.8.4).
4232
        if ($num > 0) {
4233
            $i = 1;
4234
            while ($row = Database::fetch_array($res)) {
4235
                $max = $i;
4236
                if ($row['display_order'] != $i) {
4237
                    // If we find a gap in the order, we need to fix it.
4238
                    $sql = "UPDATE $lp_table SET display_order = $i
4239
                              WHERE iid = ".$row['iid'];
4240
                    Database::query($sql);
4241
                }
4242
                $row['display_order'] = $i;
4243
                $lps[$row['iid']] = $row;
4244
                $lp_order[$i] = $row['iid'];
4245
                $i++;
4246
            }
4247
        }
4248
        if ($num > 1) { // If there's only one element, no need to sort.
4249
            $order = $lps[$lp_id]['display_order'];
4250
            if ($order < $max) { // If it's the first element, no need to move up.
4251
                $sql = "UPDATE $lp_table SET display_order = $order
4252
                        WHERE iid = ".$lp_order[$order + 1];
4253
                Database::query($sql);
4254
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4255
                        WHERE iid = $lp_id";
4256
                Database::query($sql);
4257
            }
4258
        }
4259
4260
        return true;
4261
    }
4262
4263
    /**
4264
     * Updates learnpath attributes to point to the next element
4265
     * The last part is similar to set_current_item but processing the other way around.
4266
     */
4267
    public function next()
4268
    {
4269
        if ($this->debug > 0) {
4270
            error_log('In learnpath::next()', 0);
4271
        }
4272
        $this->last = $this->get_current_item_id();
4273
        $this->items[$this->last]->save(
4274
            false,
4275
            $this->prerequisites_match($this->last)
4276
        );
4277
        $this->autocomplete_parents($this->last);
4278
        $new_index = $this->get_next_index();
4279
        if ($this->debug > 2) {
4280
            error_log('New index: '.$new_index, 0);
4281
        }
4282
        $this->index = $new_index;
4283
        if ($this->debug > 2) {
4284
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4285
        }
4286
        $this->current = $this->ordered_items[$new_index];
4287
        if ($this->debug > 2) {
4288
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4289
        }
4290
    }
4291
4292
    /**
4293
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4294
     * class, this might be redefined to allow several behaviours depending on the document type.
4295
     *
4296
     * @param int $id Resource ID
4297
     */
4298
    public function open($id)
4299
    {
4300
        if ($this->debug > 0) {
4301
            error_log('In learnpath::open()', 0);
4302
        }
4303
        // TODO:
4304
        // set the current resource attribute to this resource
4305
        // switch on element type (redefine in child class?)
4306
        // set status for this item to "opened"
4307
        // start timer
4308
        // initialise score
4309
        $this->index = 0; //or = the last item seen (see $this->last)
4310
    }
4311
4312
    /**
4313
     * Check that all prerequisites are fulfilled. Returns true and an
4314
     * empty string on success, returns false
4315
     * and the prerequisite string on error.
4316
     * This function is based on the rules for aicc_script language as
4317
     * described in the SCORM 1.2 CAM documentation page 108.
4318
     *
4319
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4320
     *
4321
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4322
     *              string otherwise
4323
     */
4324
    public function prerequisites_match($itemId = null)
4325
    {
4326
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4327
        if ($allow) {
4328
            if (api_is_allowed_to_edit() ||
4329
                api_is_platform_admin(true) ||
4330
                api_is_drh() ||
4331
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4332
            ) {
4333
                return true;
4334
            }
4335
        }
4336
4337
        $debug = $this->debug;
4338
        if ($debug > 0) {
4339
            error_log('In learnpath::prerequisites_match()', 0);
4340
        }
4341
4342
        if (empty($itemId)) {
4343
            $itemId = $this->current;
4344
        }
4345
4346
        $currentItem = $this->getItem($itemId);
4347
4348
        if ($currentItem) {
4349
            if ($this->type == 2) {
4350
                // Getting prereq from scorm
4351
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4352
            } else {
4353
                $prereq_string = $currentItem->get_prereq_string();
4354
            }
4355
4356
            if (empty($prereq_string)) {
4357
                if ($debug > 0) {
4358
                    error_log('Found prereq_string is empty return true');
4359
                }
4360
4361
                return true;
4362
            }
4363
            // Clean spaces.
4364
            $prereq_string = str_replace(' ', '', $prereq_string);
4365
            if ($debug > 0) {
4366
                error_log('Found prereq_string: '.$prereq_string, 0);
4367
            }
4368
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4369
            $result = $currentItem->parse_prereq(
4370
                $prereq_string,
4371
                $this->items,
4372
                $this->refs_list,
4373
                $this->get_user_id()
4374
            );
4375
4376
            if ($result === false) {
4377
                $this->set_error_msg($currentItem->prereq_alert);
4378
            }
4379
        } else {
4380
            $result = true;
4381
            if ($debug > 1) {
4382
                error_log('$this->items['.$itemId.'] was not an object', 0);
4383
            }
4384
        }
4385
4386
        if ($debug > 1) {
4387
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4388
        }
4389
4390
        return $result;
4391
    }
4392
4393
    /**
4394
     * Updates learnpath attributes to point to the previous element
4395
     * The last part is similar to set_current_item but processing the other way around.
4396
     */
4397
    public function previous()
4398
    {
4399
        if ($this->debug > 0) {
4400
            error_log('In learnpath::previous()', 0);
4401
        }
4402
        $this->last = $this->get_current_item_id();
4403
        $this->items[$this->last]->save(
4404
            false,
4405
            $this->prerequisites_match($this->last)
4406
        );
4407
        $this->autocomplete_parents($this->last);
4408
        $new_index = $this->get_previous_index();
4409
        $this->index = $new_index;
4410
        $this->current = $this->ordered_items[$new_index];
4411
    }
4412
4413
    /**
4414
     * Publishes a learnpath. This basically means show or hide the learnpath
4415
     * to normal users.
4416
     * Can be used as abstract.
4417
     *
4418
     * @param int $lp_id          Learnpath ID
4419
     * @param int $set_visibility New visibility
4420
     *
4421
     * @return bool
4422
     */
4423
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4424
    {
4425
        $action = 'visible';
4426
        if ($set_visibility != 1) {
4427
            $action = 'invisible';
4428
            self::toggle_publish($lp_id, 'i');
4429
        }
4430
4431
        return api_item_property_update(
4432
            api_get_course_info(),
4433
            TOOL_LEARNPATH,
4434
            $lp_id,
4435
            $action,
4436
            api_get_user_id()
4437
        );
4438
    }
4439
4440
    /**
4441
     * Publishes a learnpath category.
4442
     * This basically means show or hide the learnpath category to normal users.
4443
     *
4444
     * @param int $id
4445
     * @param int $visibility
4446
     *
4447
     * @throws \Doctrine\ORM\NonUniqueResultException
4448
     * @throws \Doctrine\ORM\ORMException
4449
     * @throws \Doctrine\ORM\OptimisticLockException
4450
     * @throws \Doctrine\ORM\TransactionRequiredException
4451
     *
4452
     * @return bool
4453
     */
4454
    public static function toggleCategoryVisibility($id, $visibility = 1)
4455
    {
4456
        $action = 'visible';
4457
4458
        if ($visibility != 1) {
4459
            $action = 'invisible';
4460
            $list = new LearnpathList(
4461
                api_get_user_id(),
4462
                null,
4463
                null,
4464
                null,
4465
                false,
4466
                $id
4467
            );
4468
4469
            $lpList = $list->get_flat_list();
4470
            foreach ($lpList as $lp) {
4471
                self::toggle_visibility($lp['iid'], 0);
4472
            }
4473
4474
            self::toggleCategoryPublish($id, 0);
4475
        }
4476
4477
        return api_item_property_update(
4478
            api_get_course_info(),
4479
            TOOL_LEARNPATH_CATEGORY,
4480
            $id,
4481
            $action,
4482
            api_get_user_id()
4483
        );
4484
    }
4485
4486
    /**
4487
     * Publishes a learnpath. This basically means show or hide the learnpath
4488
     * on the course homepage
4489
     * Can be used as abstract.
4490
     *
4491
     * @param int    $lp_id          Learnpath id
4492
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4493
     *
4494
     * @return bool
4495
     */
4496
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4497
    {
4498
        $course_id = api_get_course_int_id();
4499
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4500
        $lp_id = (int) $lp_id;
4501
        $sql = "SELECT * FROM $tbl_lp
4502
                WHERE iid = $lp_id";
4503
        $result = Database::query($sql);
4504
        if (Database::num_rows($result)) {
4505
            $row = Database::fetch_array($result);
4506
            $name = Database::escape_string($row['name']);
4507
            if ($set_visibility == 'i') {
4508
                $v = 0;
4509
            }
4510
            if ($set_visibility == 'v') {
4511
                $v = 1;
4512
            }
4513
4514
            $session_id = api_get_session_id();
4515
            $session_condition = api_get_session_condition($session_id);
4516
4517
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4518
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4519
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4520
4521
            $sql = "SELECT * FROM $tbl_tool
4522
                    WHERE
4523
                        c_id = $course_id AND
4524
                        (link = '$link' OR link = '$oldLink') AND
4525
                        image = 'scormbuilder.gif' AND
4526
                        (
4527
                            link LIKE '$link%' OR
4528
                            link LIKE '$oldLink%'
4529
                        )
4530
                        $session_condition
4531
                    ";
4532
4533
            $result = Database::query($sql);
4534
            $num = Database::num_rows($result);
4535
            if ($set_visibility == 'i' && $num > 0) {
4536
                $sql = "DELETE FROM $tbl_tool
4537
                        WHERE 
4538
                            c_id = $course_id AND 
4539
                            (link = '$link' OR link = '$oldLink') AND 
4540
                            image='scormbuilder.gif' 
4541
                            $session_condition";
4542
                Database::query($sql);
4543
            } elseif ($set_visibility == 'v' && $num == 0) {
4544
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4545
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4546
                Database::query($sql);
4547
                $insertId = Database::insert_id();
4548
                if ($insertId) {
4549
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4550
                    Database::query($sql);
4551
                }
4552
            } elseif ($set_visibility == 'v' && $num > 0) {
4553
                $sql = "UPDATE $tbl_tool SET
4554
                            c_id = $course_id,
4555
                            name = '$name',
4556
                            link = '$link',
4557
                            image = 'scormbuilder.gif',
4558
                            visibility = '$v',
4559
                            admin = '0',
4560
                            address = 'pastillegris.gif',
4561
                            added_tool = 0,
4562
                            session_id = $session_id
4563
                        WHERE
4564
                            c_id = ".$course_id." AND
4565
                            (link = '$link' OR link = '$oldLink') AND 
4566
                            image='scormbuilder.gif' 
4567
                            $session_condition
4568
                        ";
4569
                Database::query($sql);
4570
            } else {
4571
                // Parameter and database incompatible, do nothing, exit.
4572
                return false;
4573
            }
4574
        } else {
4575
            return false;
4576
        }
4577
    }
4578
4579
    /**
4580
     * Publishes a learnpath.
4581
     * Show or hide the learnpath category on the course homepage.
4582
     *
4583
     * @param int $id
4584
     * @param int $setVisibility
4585
     *
4586
     * @throws \Doctrine\ORM\NonUniqueResultException
4587
     * @throws \Doctrine\ORM\ORMException
4588
     * @throws \Doctrine\ORM\OptimisticLockException
4589
     * @throws \Doctrine\ORM\TransactionRequiredException
4590
     *
4591
     * @return bool
4592
     */
4593
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4594
    {
4595
        $courseId = api_get_course_int_id();
4596
        $sessionId = api_get_session_id();
4597
        $sessionCondition = api_get_session_condition(
4598
            $sessionId,
4599
            true,
4600
            false,
4601
            't.sessionId'
4602
        );
4603
4604
        $em = Database::getManager();
4605
4606
        /** @var CLpCategory $category */
4607
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4608
4609
        if (!$category) {
4610
            return false;
4611
        }
4612
4613
        if (empty($courseId)) {
4614
            return false;
4615
        }
4616
4617
        $link = self::getCategoryLinkForTool($id);
4618
4619
        /** @var CTool $tool */
4620
        $tool = $em->createQuery("
4621
                SELECT t FROM ChamiloCourseBundle:CTool t
4622
                WHERE
4623
                    t.course = :course AND
4624
                    t.link = :link1 AND
4625
                    t.image LIKE 'lp_category.%' AND
4626
                    t.link LIKE :link2
4627
                    $sessionCondition
4628
            ")
4629
            ->setParameters([
4630
                'course' => $courseId,
4631
                'link1' => $link,
4632
                'link2' => "$link%",
4633
            ])
4634
            ->getOneOrNullResult();
4635
4636
        if ($setVisibility == 0 && $tool) {
4637
            $em->remove($tool);
4638
            $em->flush();
4639
4640
            return true;
4641
        }
4642
4643
        if ($setVisibility == 1 && !$tool) {
4644
            $tool = new CTool();
4645
            $tool
4646
                ->setCategory('authoring')
4647
                ->setCourse(api_get_course_entity($courseId))
4648
                ->setName(strip_tags($category->getName()))
4649
                ->setLink($link)
4650
                ->setImage('lp_category.png')
4651
                ->setVisibility(1)
4652
                ->setAdmin(0)
4653
                ->setAddress('pastillegris.gif')
4654
                ->setAddedTool(0)
4655
                ->setSessionId($sessionId)
4656
                ->setTarget('_self');
4657
4658
            $em->persist($tool);
4659
            $em->flush();
4660
4661
            $tool->setId($tool->getIid());
4662
4663
            $em->persist($tool);
4664
            $em->flush();
4665
4666
            return true;
4667
        }
4668
4669
        if ($setVisibility == 1 && $tool) {
4670
            $tool
4671
                ->setName(strip_tags($category->getName()))
4672
                ->setVisibility(1);
4673
4674
            $em->persist($tool);
4675
            $em->flush();
4676
4677
            return true;
4678
        }
4679
4680
        return false;
4681
    }
4682
4683
    /**
4684
     * Check if the learnpath category is visible for a user.
4685
     *
4686
     * @param CLpCategory $category
4687
     * @param User        $user
4688
     *
4689
     * @return bool
4690
     */
4691
    public static function categoryIsVisibleForStudent(
4692
        CLpCategory $category,
4693
        User $user
4694
    ) {
4695
        $subscriptionSettings = self::getSubscriptionSettings();
4696
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4697
            return true;
4698
        }
4699
4700
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4701
4702
        if ($isAllowedToEdit) {
4703
            return true;
4704
        }
4705
4706
        if (empty($category)) {
4707
            return false;
4708
        }
4709
4710
        $users = $category->getUsers();
4711
4712
        if (empty($users) || !$users->count()) {
4713
            return true;
4714
        }
4715
4716
        if ($category->hasUserAdded($user)) {
4717
            return true;
4718
        }
4719
4720
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4721
        if (!empty($groups)) {
4722
            $em = Database::getManager();
4723
4724
            /** @var ItemPropertyRepository $itemRepo */
4725
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4726
4727
            /** @var CourseRepository $courseRepo */
4728
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4729
            $sessionId = api_get_session_id();
4730
            $session = null;
4731
            if (!empty($sessionId)) {
4732
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4733
            }
4734
4735
            $course = $courseRepo->find(api_get_course_int_id());
4736
4737
            // Subscribed groups to a LP
4738
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4739
                TOOL_LEARNPATH_CATEGORY,
4740
                $category->getId(),
4741
                $course,
4742
                $session
4743
            );
4744
4745
            if (!empty($subscribedGroupsInLp)) {
4746
                $groups = array_column($groups, 'iid');
4747
                /** @var CItemProperty $item */
4748
                foreach ($subscribedGroupsInLp as $item) {
4749
                    if ($item->getGroup() &&
4750
                        in_array($item->getGroup()->getId(), $groups)
4751
                    ) {
4752
                        return true;
4753
                    }
4754
                }
4755
            }
4756
        }
4757
4758
        return false;
4759
    }
4760
4761
    /**
4762
     * Check if a learnpath category is published as course tool.
4763
     *
4764
     * @param CLpCategory $category
4765
     * @param int         $courseId
4766
     *
4767
     * @return bool
4768
     */
4769
    public static function categoryIsPublished(
4770
        CLpCategory $category,
4771
        $courseId
4772
    ) {
4773
        $link = self::getCategoryLinkForTool($category->getId());
4774
        $em = Database::getManager();
4775
4776
        $tools = $em
4777
            ->createQuery("
4778
                SELECT t FROM ChamiloCourseBundle:CTool t
4779
                WHERE t.course = :course AND 
4780
                    t.name = :name AND
4781
                    t.image LIKE 'lp_category.%' AND
4782
                    t.link LIKE :link
4783
            ")
4784
            ->setParameters([
4785
                'course' => $courseId,
4786
                'name' => strip_tags($category->getName()),
4787
                'link' => "$link%",
4788
            ])
4789
            ->getResult();
4790
4791
        /** @var CTool $tool */
4792
        $tool = current($tools);
4793
4794
        return $tool ? $tool->getVisibility() : false;
4795
    }
4796
4797
    /**
4798
     * Restart the whole learnpath. Return the URL of the first element.
4799
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4800
     * To use a similar method  statically, use the create_new_attempt() method.
4801
     *
4802
     * @return bool
4803
     */
4804
    public function restart()
4805
    {
4806
        if ($this->debug > 0) {
4807
            error_log('In learnpath::restart()', 0);
4808
        }
4809
        // TODO
4810
        // Call autosave method to save the current progress.
4811
        //$this->index = 0;
4812
        if (api_is_invitee()) {
4813
            return false;
4814
        }
4815
        $session_id = api_get_session_id();
4816
        $course_id = api_get_course_int_id();
4817
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4818
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4819
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4820
        if ($this->debug > 2) {
4821
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4822
        }
4823
        Database::query($sql);
4824
        $view_id = Database::insert_id();
4825
4826
        if ($view_id) {
4827
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4828
            Database::query($sql);
4829
            $this->lp_view_id = $view_id;
4830
            $this->attempt = $this->attempt + 1;
4831
        } else {
4832
            $this->error = 'Could not insert into item_view table...';
4833
4834
            return false;
4835
        }
4836
        $this->autocomplete_parents($this->current);
4837
        foreach ($this->items as $index => $dummy) {
4838
            $this->items[$index]->restart();
4839
            $this->items[$index]->set_lp_view($this->lp_view_id);
4840
        }
4841
        $this->first();
4842
4843
        return true;
4844
    }
4845
4846
    /**
4847
     * Saves the current item.
4848
     *
4849
     * @return bool
4850
     */
4851
    public function save_current()
4852
    {
4853
        $debug = $this->debug;
4854
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4855
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4856
        if ($debug) {
4857
            error_log('save_current() saving item '.$this->current, 0);
4858
            error_log(''.print_r($this->items, true), 0);
4859
        }
4860
        if (isset($this->items[$this->current]) &&
4861
            is_object($this->items[$this->current])
4862
        ) {
4863
            if ($debug) {
4864
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4865
            }
4866
4867
            $res = $this->items[$this->current]->save(
4868
                false,
4869
                $this->prerequisites_match($this->current)
4870
            );
4871
            $this->autocomplete_parents($this->current);
4872
            $status = $this->items[$this->current]->get_status();
4873
            $this->update_queue[$this->current] = $status;
4874
4875
            if ($debug) {
4876
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4877
            }
4878
4879
            return $res;
4880
        }
4881
4882
        return false;
4883
    }
4884
4885
    /**
4886
     * Saves the given item.
4887
     *
4888
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4889
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4890
     *
4891
     * @return bool
4892
     */
4893
    public function save_item($item_id = null, $from_outside = true)
4894
    {
4895
        $debug = $this->debug;
4896
        if ($debug) {
4897
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4898
        }
4899
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4900
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4901
        if (empty($item_id)) {
4902
            $item_id = (int) $_REQUEST['id'];
4903
        }
4904
4905
        if (empty($item_id)) {
4906
            $item_id = $this->get_current_item_id();
4907
        }
4908
        if (isset($this->items[$item_id]) &&
4909
            is_object($this->items[$item_id])
4910
        ) {
4911
            if ($debug) {
4912
                error_log('Object exists');
4913
            }
4914
4915
            // Saving the item.
4916
            $res = $this->items[$item_id]->save(
4917
                $from_outside,
4918
                $this->prerequisites_match($item_id)
4919
            );
4920
4921
            if ($debug) {
4922
                error_log('update_queue before:');
4923
                error_log(print_r($this->update_queue, 1));
4924
            }
4925
            $this->autocomplete_parents($item_id);
4926
4927
            $status = $this->items[$item_id]->get_status();
4928
            $this->update_queue[$item_id] = $status;
4929
4930
            if ($debug) {
4931
                error_log('get_status(): '.$status);
4932
                error_log('update_queue after:');
4933
                error_log(print_r($this->update_queue, 1));
4934
            }
4935
4936
            return $res;
4937
        }
4938
4939
        return false;
4940
    }
4941
4942
    /**
4943
     * Saves the last item seen's ID only in case.
4944
     */
4945
    public function save_last()
4946
    {
4947
        $course_id = api_get_course_int_id();
4948
        $debug = $this->debug;
4949
        if ($debug) {
4950
            error_log('In learnpath::save_last()', 0);
4951
        }
4952
        $session_condition = api_get_session_condition(
4953
            api_get_session_id(),
4954
            true,
4955
            false
4956
        );
4957
        $table = Database::get_course_table(TABLE_LP_VIEW);
4958
4959
        if (isset($this->current) && !api_is_invitee()) {
4960
            if ($debug) {
4961
                error_log('Saving current item ('.$this->current.') for later review', 0);
4962
            }
4963
            $sql = "UPDATE $table SET
4964
                        last_item = ".intval($this->get_current_item_id())."
4965
                    WHERE
4966
                        c_id = $course_id AND
4967
                        lp_id = ".$this->get_id()." AND
4968
                        user_id = ".$this->get_user_id()." ".$session_condition;
4969
4970
            if ($debug) {
4971
                error_log('Saving last item seen : '.$sql, 0);
4972
            }
4973
            Database::query($sql);
4974
        }
4975
4976
        if (!api_is_invitee()) {
4977
            // Save progress.
4978
            list($progress) = $this->get_progress_bar_text('%');
4979
            if ($progress >= 0 && $progress <= 100) {
4980
                $progress = (int) $progress;
4981
                $sql = "UPDATE $table SET
4982
                            progress = $progress
4983
                        WHERE
4984
                            c_id = $course_id AND
4985
                            lp_id = ".$this->get_id()." AND
4986
                            user_id = ".$this->get_user_id()." ".$session_condition;
4987
                // Ignore errors as some tables might not have the progress field just yet.
4988
                Database::query($sql);
4989
                if ($debug) {
4990
                    error_log($sql);
4991
                }
4992
                $this->progress_db = $progress;
4993
            }
4994
        }
4995
    }
4996
4997
    /**
4998
     * Sets the current item ID (checks if valid and authorized first).
4999
     *
5000
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5001
     */
5002
    public function set_current_item($item_id = null)
5003
    {
5004
        $debug = $this->debug;
5005
        if ($debug) {
5006
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5007
        }
5008
        if (empty($item_id)) {
5009
            if ($debug) {
5010
                error_log('No new current item given, ignore...', 0);
5011
            }
5012
            // Do nothing.
5013
        } else {
5014
            if ($debug) {
5015
                error_log('New current item given is '.$item_id.'...', 0);
5016
            }
5017
            if (is_numeric($item_id)) {
5018
                $item_id = (int) $item_id;
5019
                // TODO: Check in database here.
5020
                $this->last = $this->current;
5021
                $this->current = $item_id;
5022
                // TODO: Update $this->index as well.
5023
                foreach ($this->ordered_items as $index => $item) {
5024
                    if ($item == $this->current) {
5025
                        $this->index = $index;
5026
                        break;
5027
                    }
5028
                }
5029
                if ($debug) {
5030
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5031
                }
5032
            } else {
5033
                if ($debug) {
5034
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5035
                }
5036
            }
5037
        }
5038
    }
5039
5040
    /**
5041
     * Sets the encoding.
5042
     *
5043
     * @param string $enc New encoding
5044
     *
5045
     * @return bool
5046
     *
5047
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5048
     */
5049
    public function set_encoding($enc = 'UTF-8')
5050
    {
5051
        if ($this->debug > 0) {
5052
            error_log('In learnpath::set_encoding()', 0);
5053
        }
5054
5055
        $enc = api_refine_encoding_id($enc);
5056
        if (empty($enc)) {
5057
            $enc = api_get_system_encoding();
5058
        }
5059
        if (api_is_encoding_supported($enc)) {
5060
            $lp = $this->get_id();
5061
            if ($lp != 0) {
5062
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5063
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5064
                        WHERE iid = ".$lp;
5065
                $res = Database::query($sql);
5066
5067
                return $res;
5068
            }
5069
        }
5070
5071
        return false;
5072
    }
5073
5074
    /**
5075
     * Sets the JS lib setting in the database directly.
5076
     * This is the JavaScript library file this lp needs to load on startup.
5077
     *
5078
     * @param string $lib Proximity setting
5079
     *
5080
     * @return bool True on update success. False otherwise.
5081
     */
5082
    public function set_jslib($lib = '')
5083
    {
5084
        if ($this->debug > 0) {
5085
            error_log('In learnpath::set_jslib()', 0);
5086
        }
5087
        $lp = $this->get_id();
5088
5089
        if ($lp != 0) {
5090
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5091
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5092
                    WHERE iid = $lp";
5093
            $res = Database::query($sql);
5094
5095
            return $res;
5096
        } else {
5097
            return false;
5098
        }
5099
    }
5100
5101
    /**
5102
     * Sets the name of the LP maker (publisher) (and save).
5103
     *
5104
     * @param string $name Optional string giving the new content_maker of this learnpath
5105
     *
5106
     * @return bool True
5107
     */
5108
    public function set_maker($name = '')
5109
    {
5110
        if ($this->debug > 0) {
5111
            error_log('In learnpath::set_maker()', 0);
5112
        }
5113
        if (empty($name)) {
5114
            return false;
5115
        }
5116
        $this->maker = $name;
5117
        $table = Database::get_course_table(TABLE_LP_MAIN);
5118
        $lp_id = $this->get_id();
5119
        $sql = "UPDATE $table SET
5120
                content_maker = '".Database::escape_string($this->maker)."'
5121
                WHERE iid = $lp_id";
5122
        if ($this->debug > 2) {
5123
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5124
        }
5125
        Database::query($sql);
5126
5127
        return true;
5128
    }
5129
5130
    /**
5131
     * Sets the name of the current learnpath (and save).
5132
     *
5133
     * @param string $name Optional string giving the new name of this learnpath
5134
     *
5135
     * @return bool True/False
5136
     */
5137
    public function set_name($name = null)
5138
    {
5139
        if ($this->debug > 0) {
5140
            error_log('In learnpath::set_name()', 0);
5141
        }
5142
        if (empty($name)) {
5143
            return false;
5144
        }
5145
        $this->name = Database::escape_string($name);
5146
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5147
        $lp_id = $this->get_id();
5148
        $course_id = $this->course_info['real_id'];
5149
        $sql = "UPDATE $lp_table SET
5150
                name = '".Database::escape_string($this->name)."'
5151
                WHERE iid = $lp_id";
5152
        if ($this->debug > 2) {
5153
            error_log('lp updated with new name : '.$this->name, 0);
5154
        }
5155
        $result = Database::query($sql);
5156
        // If the lp is visible on the homepage, change his name there.
5157
        if (Database::affected_rows($result)) {
5158
            $session_id = api_get_session_id();
5159
            $session_condition = api_get_session_condition($session_id);
5160
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5161
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5162
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5163
            	    WHERE
5164
            	        c_id = $course_id AND
5165
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5166
            Database::query($sql);
5167
5168
            return true;
5169
        } else {
5170
            return false;
5171
        }
5172
    }
5173
5174
    /**
5175
     * Set index specified prefix terms for all items in this path.
5176
     *
5177
     * @param string $terms_string Comma-separated list of terms
5178
     * @param string $prefix       Xapian term prefix
5179
     *
5180
     * @return bool False on error, true otherwise
5181
     */
5182
    public function set_terms_by_prefix($terms_string, $prefix)
5183
    {
5184
        $course_id = api_get_course_int_id();
5185
        if (api_get_setting('search_enabled') !== 'true') {
5186
            return false;
5187
        }
5188
5189
        if (!extension_loaded('xapian')) {
5190
            return false;
5191
        }
5192
5193
        $terms_string = trim($terms_string);
5194
        $terms = explode(',', $terms_string);
5195
        array_walk($terms, 'trim_value');
5196
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5197
5198
        // Don't do anything if no change, verify only at DB, not the search engine.
5199
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5200
            return false;
5201
        }
5202
5203
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5204
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5205
5206
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5207
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5208
        $lp_id = (int) $_POST['lp_id'];
5209
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5210
        $result = Database::query($sql);
5211
        $di = new ChamiloIndexer();
5212
5213
        while ($lp_item = Database::fetch_array($result)) {
5214
            // Get search_did.
5215
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5216
            $sql = 'SELECT * FROM %s
5217
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5218
                    LIMIT 1';
5219
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5220
5221
            //echo $sql; echo '<br>';
5222
            $res = Database::query($sql);
5223
            if (Database::num_rows($res) > 0) {
5224
                $se_ref = Database::fetch_array($res);
5225
                // Compare terms.
5226
                $doc = $di->get_document($se_ref['search_did']);
5227
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5228
                $xterms = [];
5229
                foreach ($xapian_terms as $xapian_term) {
5230
                    $xterms[] = substr($xapian_term['name'], 1);
5231
                }
5232
5233
                $dterms = $terms;
5234
                $missing_terms = array_diff($dterms, $xterms);
5235
                $deprecated_terms = array_diff($xterms, $dterms);
5236
5237
                // Save it to search engine.
5238
                foreach ($missing_terms as $term) {
5239
                    $doc->add_term($prefix.$term, 1);
5240
                }
5241
                foreach ($deprecated_terms as $term) {
5242
                    $doc->remove_term($prefix.$term);
5243
                }
5244
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5245
                $di->getDb()->flush();
5246
            }
5247
        }
5248
5249
        return true;
5250
    }
5251
5252
    /**
5253
     * Sets the theme of the LP (local/remote) (and save).
5254
     *
5255
     * @param string $name Optional string giving the new theme of this learnpath
5256
     *
5257
     * @return bool Returns true if theme name is not empty
5258
     */
5259
    public function set_theme($name = '')
5260
    {
5261
        if ($this->debug > 0) {
5262
            error_log('In learnpath::set_theme()', 0);
5263
        }
5264
        $this->theme = $name;
5265
        $table = Database::get_course_table(TABLE_LP_MAIN);
5266
        $lp_id = $this->get_id();
5267
        $sql = "UPDATE $table 
5268
                SET theme = '".Database::escape_string($this->theme)."'
5269
                WHERE iid = $lp_id";
5270
        if ($this->debug > 2) {
5271
            error_log('lp updated with new theme : '.$this->theme, 0);
5272
        }
5273
        Database::query($sql);
5274
5275
        return true;
5276
    }
5277
5278
    /**
5279
     * Sets the image of an LP (and save).
5280
     *
5281
     * @param string $name Optional string giving the new image of this learnpath
5282
     *
5283
     * @return bool Returns true if theme name is not empty
5284
     */
5285
    public function set_preview_image($name = '')
5286
    {
5287
        if ($this->debug > 0) {
5288
            error_log('In learnpath::set_preview_image()', 0);
5289
        }
5290
5291
        $this->preview_image = $name;
5292
        $table = Database::get_course_table(TABLE_LP_MAIN);
5293
        $lp_id = $this->get_id();
5294
        $sql = "UPDATE $table SET
5295
                preview_image = '".Database::escape_string($this->preview_image)."'
5296
                WHERE iid = $lp_id";
5297
        if ($this->debug > 2) {
5298
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5299
        }
5300
        Database::query($sql);
5301
5302
        return true;
5303
    }
5304
5305
    /**
5306
     * Sets the author of a LP (and save).
5307
     *
5308
     * @param string $name Optional string giving the new author of this learnpath
5309
     *
5310
     * @return bool Returns true if author's name is not empty
5311
     */
5312
    public function set_author($name = '')
5313
    {
5314
        if ($this->debug > 0) {
5315
            error_log('In learnpath::set_author()', 0);
5316
        }
5317
        $this->author = $name;
5318
        $table = Database::get_course_table(TABLE_LP_MAIN);
5319
        $lp_id = $this->get_id();
5320
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5321
                WHERE iid = $lp_id";
5322
        if ($this->debug > 2) {
5323
            error_log('lp updated with new preview author : '.$this->author, 0);
5324
        }
5325
        Database::query($sql);
5326
5327
        return true;
5328
    }
5329
5330
    /**
5331
     * Sets the hide_toc_frame parameter of a LP (and save).
5332
     *
5333
     * @param int $hide 1 if frame is hidden 0 then else
5334
     *
5335
     * @return bool Returns true if author's name is not empty
5336
     */
5337
    public function set_hide_toc_frame($hide)
5338
    {
5339
        if ($this->debug > 0) {
5340
            error_log('In learnpath::set_hide_toc_frame()', 0);
5341
        }
5342
        if (intval($hide) == $hide) {
5343
            $this->hide_toc_frame = $hide;
5344
            $table = Database::get_course_table(TABLE_LP_MAIN);
5345
            $lp_id = $this->get_id();
5346
            $sql = "UPDATE $table SET
5347
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5348
                    WHERE iid = $lp_id";
5349
            if ($this->debug > 2) {
5350
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5351
            }
5352
            Database::query($sql);
5353
5354
            return true;
5355
        } else {
5356
            return false;
5357
        }
5358
    }
5359
5360
    /**
5361
     * Sets the prerequisite of a LP (and save).
5362
     *
5363
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5364
     *
5365
     * @return bool returns true if prerequisite is not empty
5366
     */
5367
    public function set_prerequisite($prerequisite)
5368
    {
5369
        if ($this->debug > 0) {
5370
            error_log('In learnpath::set_prerequisite()', 0);
5371
        }
5372
        $this->prerequisite = (int) $prerequisite;
5373
        $table = Database::get_course_table(TABLE_LP_MAIN);
5374
        $lp_id = $this->get_id();
5375
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5376
                WHERE iid = $lp_id";
5377
        if ($this->debug > 2) {
5378
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5379
        }
5380
        Database::query($sql);
5381
5382
        return true;
5383
    }
5384
5385
    /**
5386
     * Sets the location/proximity of the LP (local/remote) (and save).
5387
     *
5388
     * @param string $name Optional string giving the new location of this learnpath
5389
     *
5390
     * @return bool True on success / False on error
5391
     */
5392
    public function set_proximity($name = '')
5393
    {
5394
        if ($this->debug > 0) {
5395
            error_log('In learnpath::set_proximity()', 0);
5396
        }
5397
        if (empty($name)) {
5398
            return false;
5399
        }
5400
5401
        $this->proximity = $name;
5402
        $table = Database::get_course_table(TABLE_LP_MAIN);
5403
        $lp_id = $this->get_id();
5404
        $sql = "UPDATE $table SET
5405
                    content_local = '".Database::escape_string($name)."'
5406
                WHERE iid = $lp_id";
5407
        if ($this->debug > 2) {
5408
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5409
        }
5410
        Database::query($sql);
5411
5412
        return true;
5413
    }
5414
5415
    /**
5416
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5417
     *
5418
     * @param int $id DB ID of the item
5419
     */
5420
    public function set_previous_item($id)
5421
    {
5422
        if ($this->debug > 0) {
5423
            error_log('In learnpath::set_previous_item()', 0);
5424
        }
5425
        $this->last = $id;
5426
    }
5427
5428
    /**
5429
     * Sets use_max_score.
5430
     *
5431
     * @param int $use_max_score Optional string giving the new location of this learnpath
5432
     *
5433
     * @return bool True on success / False on error
5434
     */
5435
    public function set_use_max_score($use_max_score = 1)
5436
    {
5437
        if ($this->debug > 0) {
5438
            error_log('In learnpath::set_use_max_score()', 0);
5439
        }
5440
        $use_max_score = (int) $use_max_score;
5441
        $this->use_max_score = $use_max_score;
5442
        $table = Database::get_course_table(TABLE_LP_MAIN);
5443
        $lp_id = $this->get_id();
5444
        $sql = "UPDATE $table SET
5445
                    use_max_score = '".$this->use_max_score."'
5446
                WHERE iid = $lp_id";
5447
5448
        if ($this->debug > 2) {
5449
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5450
        }
5451
        Database::query($sql);
5452
5453
        return true;
5454
    }
5455
5456
    /**
5457
     * Sets and saves the expired_on date.
5458
     *
5459
     * @param string $expired_on Optional string giving the new author of this learnpath
5460
     *
5461
     * @throws \Doctrine\ORM\OptimisticLockException
5462
     *
5463
     * @return bool Returns true if author's name is not empty
5464
     */
5465
    public function set_expired_on($expired_on)
5466
    {
5467
        if ($this->debug > 0) {
5468
            error_log('In learnpath::set_expired_on()', 0);
5469
        }
5470
5471
        $em = Database::getManager();
5472
        /** @var CLp $lp */
5473
        $lp = $em
5474
            ->getRepository('ChamiloCourseBundle:CLp')
5475
            ->findOneBy(
5476
                [
5477
                    'iid' => $this->get_id(),
5478
                ]
5479
            );
5480
5481
        if (!$lp) {
5482
            return false;
5483
        }
5484
5485
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5486
5487
        $lp->setExpiredOn($this->expired_on);
5488
        $em->persist($lp);
5489
        $em->flush();
5490
5491
        if ($this->debug > 2) {
5492
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5493
        }
5494
5495
        return true;
5496
    }
5497
5498
    /**
5499
     * Sets and saves the publicated_on date.
5500
     *
5501
     * @param string $publicated_on Optional string giving the new author of this learnpath
5502
     *
5503
     * @throws \Doctrine\ORM\OptimisticLockException
5504
     *
5505
     * @return bool Returns true if author's name is not empty
5506
     */
5507
    public function set_publicated_on($publicated_on)
5508
    {
5509
        if ($this->debug > 0) {
5510
            error_log('In learnpath::set_expired_on()', 0);
5511
        }
5512
5513
        $em = Database::getManager();
5514
        /** @var CLp $lp */
5515
        $lp = $em
5516
            ->getRepository('ChamiloCourseBundle:CLp')
5517
            ->findOneBy(
5518
                [
5519
                    'iid' => $this->get_id(),
5520
                ]
5521
            );
5522
5523
        if (!$lp) {
5524
            return false;
5525
        }
5526
5527
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5528
        $lp->setPublicatedOn($this->publicated_on);
5529
        $em->persist($lp);
5530
        $em->flush();
5531
5532
        if ($this->debug > 2) {
5533
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5534
        }
5535
5536
        return true;
5537
    }
5538
5539
    /**
5540
     * Sets and saves the expired_on date.
5541
     *
5542
     * @return bool Returns true if author's name is not empty
5543
     */
5544
    public function set_modified_on()
5545
    {
5546
        if ($this->debug > 0) {
5547
            error_log('In learnpath::set_expired_on()', 0);
5548
        }
5549
        $this->modified_on = api_get_utc_datetime();
5550
        $table = Database::get_course_table(TABLE_LP_MAIN);
5551
        $lp_id = $this->get_id();
5552
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5553
                WHERE iid = $lp_id";
5554
        if ($this->debug > 2) {
5555
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5556
        }
5557
        Database::query($sql);
5558
5559
        return true;
5560
    }
5561
5562
    /**
5563
     * Sets the object's error message.
5564
     *
5565
     * @param string $error Error message. If empty, reinits the error string
5566
     */
5567
    public function set_error_msg($error = '')
5568
    {
5569
        if ($this->debug > 0) {
5570
            error_log('In learnpath::set_error_msg()', 0);
5571
        }
5572
        if (empty($error)) {
5573
            $this->error = '';
5574
        } else {
5575
            $this->error .= $error;
5576
        }
5577
    }
5578
5579
    /**
5580
     * Launches the current item if not 'sco'
5581
     * (starts timer and make sure there is a record ready in the DB).
5582
     *
5583
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5584
     *
5585
     * @return bool
5586
     */
5587
    public function start_current_item($allow_new_attempt = false)
5588
    {
5589
        $debug = $this->debug;
5590
        if ($debug) {
5591
            error_log('In learnpath::start_current_item()');
5592
            error_log('current: '.$this->current);
5593
        }
5594
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5595
            $type = $this->get_type();
5596
            $item_type = $this->items[$this->current]->get_type();
5597
            if (($type == 2 && $item_type != 'sco') ||
5598
                ($type == 3 && $item_type != 'au') ||
5599
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5600
            ) {
5601
                if ($debug) {
5602
                    error_log('item type: '.$item_type);
5603
                    error_log('lp type: '.$type);
5604
                }
5605
                $this->items[$this->current]->open($allow_new_attempt);
5606
                $this->autocomplete_parents($this->current);
5607
                $prereq_check = $this->prerequisites_match($this->current);
5608
                if ($debug) {
5609
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5610
                }
5611
                $this->items[$this->current]->save(false, $prereq_check);
5612
            }
5613
            // If sco, then it is supposed to have been updated by some other call.
5614
            if ($item_type == 'sco') {
5615
                $this->items[$this->current]->restart();
5616
            }
5617
        }
5618
        if ($debug) {
5619
            error_log('lp_view_session_id');
5620
            error_log($this->lp_view_session_id);
5621
            error_log('api session id');
5622
            error_log(api_get_session_id());
5623
            error_log('End of learnpath::start_current_item()');
5624
        }
5625
5626
        return true;
5627
    }
5628
5629
    /**
5630
     * Stops the processing and counters for the old item (as held in $this->last).
5631
     *
5632
     * @return bool True/False
5633
     */
5634
    public function stop_previous_item()
5635
    {
5636
        $debug = $this->debug;
5637
        if ($debug) {
5638
            error_log('In learnpath::stop_previous_item()', 0);
5639
        }
5640
5641
        if ($this->last != 0 && $this->last != $this->current &&
5642
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5643
        ) {
5644
            if ($debug) {
5645
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5646
            }
5647
            switch ($this->get_type()) {
5648
                case '3':
5649
                    if ($this->items[$this->last]->get_type() != 'au') {
5650
                        if ($debug) {
5651
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5652
                        }
5653
                        $this->items[$this->last]->close();
5654
                    } else {
5655
                        if ($debug) {
5656
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5657
                        }
5658
                    }
5659
                    break;
5660
                case '2':
5661
                    if ($this->items[$this->last]->get_type() != 'sco') {
5662
                        if ($debug) {
5663
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5664
                        }
5665
                        $this->items[$this->last]->close();
5666
                    } else {
5667
                        if ($debug) {
5668
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5669
                        }
5670
                    }
5671
                    break;
5672
                case '1':
5673
                default:
5674
                    if ($debug) {
5675
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5676
                    }
5677
                    $this->items[$this->last]->close();
5678
                    break;
5679
            }
5680
        } else {
5681
            if ($debug) {
5682
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5683
            }
5684
5685
            return false;
5686
        }
5687
5688
        return true;
5689
    }
5690
5691
    /**
5692
     * Updates the default view mode from fullscreen to embedded and inversely.
5693
     *
5694
     * @return string The current default view mode ('fullscreen' or 'embedded')
5695
     */
5696
    public function update_default_view_mode()
5697
    {
5698
        if ($this->debug > 0) {
5699
            error_log('In learnpath::update_default_view_mode()', 0);
5700
        }
5701
        $table = Database::get_course_table(TABLE_LP_MAIN);
5702
        $sql = "SELECT * FROM $table
5703
                WHERE iid = ".$this->get_id();
5704
        $res = Database::query($sql);
5705
        if (Database::num_rows($res) > 0) {
5706
            $row = Database::fetch_array($res);
5707
            $default_view_mode = $row['default_view_mod'];
5708
            $view_mode = $default_view_mode;
5709
            switch ($default_view_mode) {
5710
                case 'fullscreen': // default with popup
5711
                    $view_mode = 'embedded';
5712
                    break;
5713
                case 'embedded': // default view with left menu
5714
                    $view_mode = 'embedframe';
5715
                    break;
5716
                case 'embedframe': //folded menu
5717
                    $view_mode = 'impress';
5718
                    break;
5719
                case 'impress':
5720
                    $view_mode = 'fullscreen';
5721
                    break;
5722
            }
5723
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5724
                    WHERE iid = ".$this->get_id();
5725
            Database::query($sql);
5726
            $this->mode = $view_mode;
5727
5728
            return $view_mode;
5729
        } else {
5730
            if ($this->debug > 2) {
5731
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5732
            }
5733
        }
5734
5735
        return -1;
5736
    }
5737
5738
    /**
5739
     * Updates the default behaviour about auto-commiting SCORM updates.
5740
     *
5741
     * @return bool True if auto-commit has been set to 'on', false otherwise
5742
     */
5743
    public function update_default_scorm_commit()
5744
    {
5745
        if ($this->debug > 0) {
5746
            error_log('In learnpath::update_default_scorm_commit()', 0);
5747
        }
5748
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5749
        $sql = "SELECT * FROM $lp_table
5750
                WHERE iid = ".$this->get_id();
5751
        $res = Database::query($sql);
5752
        if (Database::num_rows($res) > 0) {
5753
            $row = Database::fetch_array($res);
5754
            $force = $row['force_commit'];
5755
            if ($force == 1) {
5756
                $force = 0;
5757
                $force_return = false;
5758
            } elseif ($force == 0) {
5759
                $force = 1;
5760
                $force_return = true;
5761
            }
5762
            $sql = "UPDATE $lp_table SET force_commit = $force
5763
                    WHERE iid = ".$this->get_id();
5764
            Database::query($sql);
5765
            $this->force_commit = $force_return;
5766
5767
            return $force_return;
5768
        } else {
5769
            if ($this->debug > 2) {
5770
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5771
            }
5772
        }
5773
5774
        return -1;
5775
    }
5776
5777
    /**
5778
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5779
     *
5780
     * @return bool True on success, false on failure
5781
     */
5782
    public function update_display_order()
5783
    {
5784
        $course_id = api_get_course_int_id();
5785
        $table = Database::get_course_table(TABLE_LP_MAIN);
5786
        $sql = "SELECT * FROM $table 
5787
                WHERE c_id = $course_id 
5788
                ORDER BY display_order";
5789
        $res = Database::query($sql);
5790
        if ($res === false) {
5791
            return false;
5792
        }
5793
5794
        $num = Database::num_rows($res);
5795
        // First check the order is correct, globally (might be wrong because
5796
        // of versions < 1.8.4).
5797
        if ($num > 0) {
5798
            $i = 1;
5799
            while ($row = Database::fetch_array($res)) {
5800
                if ($row['display_order'] != $i) {
5801
                    // If we find a gap in the order, we need to fix it.
5802
                    $sql = "UPDATE $table SET display_order = $i
5803
                            WHERE iid = ".$row['iid'];
5804
                    Database::query($sql);
5805
                }
5806
                $i++;
5807
            }
5808
        }
5809
5810
        return true;
5811
    }
5812
5813
    /**
5814
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5815
     *
5816
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5817
     */
5818
    public function update_reinit()
5819
    {
5820
        if ($this->debug > 0) {
5821
            error_log('In learnpath::update_reinit()', 0);
5822
        }
5823
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5824
        $sql = "SELECT * FROM $lp_table
5825
                WHERE iid = ".$this->get_id();
5826
        $res = Database::query($sql);
5827
        if (Database::num_rows($res) > 0) {
5828
            $row = Database::fetch_array($res);
5829
            $force = $row['prevent_reinit'];
5830
            if ($force == 1) {
5831
                $force = 0;
5832
            } elseif ($force == 0) {
5833
                $force = 1;
5834
            }
5835
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5836
                    WHERE iid = ".$this->get_id();
5837
            Database::query($sql);
5838
            $this->prevent_reinit = $force;
5839
5840
            return $force;
5841
        } else {
5842
            if ($this->debug > 2) {
5843
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5844
            }
5845
        }
5846
5847
        return -1;
5848
    }
5849
5850
    /**
5851
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5852
     *
5853
     * @return string 'single', 'multi' or 'seriousgame'
5854
     *
5855
     * @author ndiechburg <[email protected]>
5856
     */
5857
    public function get_attempt_mode()
5858
    {
5859
        //Set default value for seriousgame_mode
5860
        if (!isset($this->seriousgame_mode)) {
5861
            $this->seriousgame_mode = 0;
5862
        }
5863
        // Set default value for prevent_reinit
5864
        if (!isset($this->prevent_reinit)) {
5865
            $this->prevent_reinit = 1;
5866
        }
5867
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5868
            return 'seriousgame';
5869
        }
5870
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5871
            return 'single';
5872
        }
5873
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5874
            return 'multiple';
5875
        }
5876
5877
        return 'single';
5878
    }
5879
5880
    /**
5881
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5882
     *
5883
     * @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...
5884
     *
5885
     * @return bool
5886
     *
5887
     * @author ndiechburg <[email protected]>
5888
     */
5889
    public function set_attempt_mode($mode)
5890
    {
5891
        switch ($mode) {
5892
            case 'seriousgame':
5893
                $sg_mode = 1;
5894
                $prevent_reinit = 1;
5895
                break;
5896
            case 'single':
5897
                $sg_mode = 0;
5898
                $prevent_reinit = 1;
5899
                break;
5900
            case 'multiple':
5901
                $sg_mode = 0;
5902
                $prevent_reinit = 0;
5903
                break;
5904
            default:
5905
                $sg_mode = 0;
5906
                $prevent_reinit = 0;
5907
                break;
5908
        }
5909
        $this->prevent_reinit = $prevent_reinit;
5910
        $this->seriousgame_mode = $sg_mode;
5911
        $table = Database::get_course_table(TABLE_LP_MAIN);
5912
        $sql = "UPDATE $table SET
5913
                prevent_reinit = $prevent_reinit ,
5914
                seriousgame_mode = $sg_mode
5915
                WHERE iid = ".$this->get_id();
5916
        $res = Database::query($sql);
5917
        if ($res) {
5918
            return true;
5919
        } else {
5920
            return false;
5921
        }
5922
    }
5923
5924
    /**
5925
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5926
     *
5927
     * @author ndiechburg <[email protected]>
5928
     */
5929
    public function switch_attempt_mode()
5930
    {
5931
        if ($this->debug > 0) {
5932
            error_log('In learnpath::switch_attempt_mode()', 0);
5933
        }
5934
        $mode = $this->get_attempt_mode();
5935
        switch ($mode) {
5936
            case 'single':
5937
                $next_mode = 'multiple';
5938
                break;
5939
            case 'multiple':
5940
                $next_mode = 'seriousgame';
5941
                break;
5942
            case 'seriousgame':
5943
                $next_mode = 'single';
5944
                break;
5945
            default:
5946
                $next_mode = 'single';
5947
                break;
5948
        }
5949
        $this->set_attempt_mode($next_mode);
5950
    }
5951
5952
    /**
5953
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5954
     * but possibility to do again a completed item.
5955
     *
5956
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5957
     *
5958
     * @author ndiechburg <[email protected]>
5959
     */
5960
    public function set_seriousgame_mode()
5961
    {
5962
        if ($this->debug > 0) {
5963
            error_log('In learnpath::set_seriousgame_mode()', 0);
5964
        }
5965
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5966
        $sql = "SELECT * FROM $lp_table 
5967
                WHERE iid = ".$this->get_id();
5968
        $res = Database::query($sql);
5969
        if (Database::num_rows($res) > 0) {
5970
            $row = Database::fetch_array($res);
5971
            $force = $row['seriousgame_mode'];
5972
            if ($force == 1) {
5973
                $force = 0;
5974
            } elseif ($force == 0) {
5975
                $force = 1;
5976
            }
5977
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5978
			        WHERE iid = ".$this->get_id();
5979
            Database::query($sql);
5980
            $this->seriousgame_mode = $force;
5981
5982
            return $force;
5983
        } else {
5984
            if ($this->debug > 2) {
5985
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
5986
            }
5987
        }
5988
5989
        return -1;
5990
    }
5991
5992
    /**
5993
     * Updates the "scorm_debug" value that shows or hide the debug window.
5994
     *
5995
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5996
     */
5997
    public function update_scorm_debug()
5998
    {
5999
        if ($this->debug > 0) {
6000
            error_log('In learnpath::update_scorm_debug()', 0);
6001
        }
6002
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6003
        $sql = "SELECT * FROM $lp_table
6004
                WHERE iid = ".$this->get_id();
6005
        $res = Database::query($sql);
6006
        if (Database::num_rows($res) > 0) {
6007
            $row = Database::fetch_array($res);
6008
            $force = $row['debug'];
6009
            if ($force == 1) {
6010
                $force = 0;
6011
            } elseif ($force == 0) {
6012
                $force = 1;
6013
            }
6014
            $sql = "UPDATE $lp_table SET debug = $force
6015
                    WHERE iid = ".$this->get_id();
6016
            Database::query($sql);
6017
            $this->scorm_debug = $force;
6018
6019
            return $force;
6020
        } else {
6021
            if ($this->debug > 2) {
6022
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6023
            }
6024
        }
6025
6026
        return -1;
6027
    }
6028
6029
    /**
6030
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6031
     *
6032
     * @author Kevin Van Den Haute
6033
     *
6034
     * @param  array
6035
     */
6036
    public function tree_array($array)
6037
    {
6038
        if ($this->debug > 1) {
6039
            error_log('In learnpath::tree_array()', 0);
6040
        }
6041
        $array = $this->sort_tree_array($array);
6042
        $this->create_tree_array($array);
6043
    }
6044
6045
    /**
6046
     * Creates an array with the elements of the learning path tree in it.
6047
     *
6048
     * @author Kevin Van Den Haute
6049
     *
6050
     * @param array $array
6051
     * @param int   $parent
6052
     * @param int   $depth
6053
     * @param array $tmp
6054
     */
6055
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6056
    {
6057
        if ($this->debug > 1) {
6058
            error_log('In learnpath::create_tree_array())', 0);
6059
        }
6060
6061
        if (is_array($array)) {
6062
            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...
6063
                if ($array[$i]['parent_item_id'] == $parent) {
6064
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6065
                        $tmp[] = $array[$i]['parent_item_id'];
6066
                        $depth++;
6067
                    }
6068
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6069
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6070
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6071
6072
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6073
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6074
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6075
                    $this->arrMenu[] = [
6076
                        'id' => $array[$i]['id'],
6077
                        'ref' => $ref,
6078
                        'item_type' => $array[$i]['item_type'],
6079
                        'title' => $array[$i]['title'],
6080
                        'path' => $path,
6081
                        'description' => $array[$i]['description'],
6082
                        'parent_item_id' => $array[$i]['parent_item_id'],
6083
                        'previous_item_id' => $array[$i]['previous_item_id'],
6084
                        'next_item_id' => $array[$i]['next_item_id'],
6085
                        'min_score' => $array[$i]['min_score'],
6086
                        'max_score' => $array[$i]['max_score'],
6087
                        'mastery_score' => $array[$i]['mastery_score'],
6088
                        'display_order' => $array[$i]['display_order'],
6089
                        'prerequisite' => $preq,
6090
                        'depth' => $depth,
6091
                        'audio' => $audio,
6092
                        'prerequisite_min_score' => $prerequisiteMinScore,
6093
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6094
                    ];
6095
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6096
                }
6097
            }
6098
        }
6099
    }
6100
6101
    /**
6102
     * Sorts a multi dimensional array by parent id and display order.
6103
     *
6104
     * @author Kevin Van Den Haute
6105
     *
6106
     * @param array $array (array with al the learning path items in it)
6107
     *
6108
     * @return array
6109
     */
6110
    public function sort_tree_array($array)
6111
    {
6112
        foreach ($array as $key => $row) {
6113
            $parent[$key] = $row['parent_item_id'];
6114
            $position[$key] = $row['display_order'];
6115
        }
6116
6117
        if (count($array) > 0) {
6118
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6119
        }
6120
6121
        return $array;
6122
    }
6123
6124
    /**
6125
     * Function that creates a html list of learning path items so that we can add audio files to them.
6126
     *
6127
     * @author Kevin Van Den Haute
6128
     *
6129
     * @return string
6130
     */
6131
    public function overview()
6132
    {
6133
        if ($this->debug > 0) {
6134
            error_log('In learnpath::overview()', 0);
6135
        }
6136
6137
        $return = '';
6138
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6139
6140
        // we need to start a form when we want to update all the mp3 files
6141
        if ($update_audio == 'true') {
6142
            $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">';
6143
        }
6144
        $return .= '<div id="message"></div>';
6145
        if (count($this->items) == 0) {
6146
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6147
        } else {
6148
            $return_audio = '<table class="data_table">';
6149
            $return_audio .= '<tr>';
6150
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6151
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6152
            $return_audio .= '</tr>';
6153
6154
            if ($update_audio != 'true') {
6155
                $return .= '<div class="col-md-12">';
6156
                $return .= self::return_new_tree($update_audio);
6157
                $return .= '</div>';
6158
                $return .= Display::div(
6159
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6160
                    ['style' => 'float:left; margin-top:15px;width:100%']
6161
                );
6162
            } else {
6163
                $return_audio .= self::return_new_tree($update_audio);
6164
                $return .= $return_audio.'</table>';
6165
            }
6166
6167
            // We need to close the form when we are updating the mp3 files.
6168
            if ($update_audio == 'true') {
6169
                $return .= '<div class="footer-audio">';
6170
                $return .= Display::button(
6171
                    'save_audio',
6172
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6173
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6174
                );
6175
                $return .= '</div>';
6176
            }
6177
        }
6178
6179
        // We need to close the form when we are updating the mp3 files.
6180
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6181
            $return .= '</form>';
6182
        }
6183
6184
        return $return;
6185
    }
6186
6187
    /**
6188
     * @param string $update_audio
6189
     *
6190
     * @return array
6191
     */
6192
    public function processBuildMenuElements($update_audio = 'false')
6193
    {
6194
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6195
        $course_id = api_get_course_int_id();
6196
        $table = Database::get_course_table(TABLE_LP_ITEM);
6197
6198
        $sql = "SELECT * FROM $table
6199
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6200
6201
        $result = Database::query($sql);
6202
        $arrLP = [];
6203
        while ($row = Database::fetch_array($result)) {
6204
            $arrLP[] = [
6205
                'id' => $row['iid'],
6206
                'item_type' => $row['item_type'],
6207
                'title' => Security::remove_XSS($row['title']),
6208
                'path' => $row['path'],
6209
                'description' => Security::remove_XSS($row['description']),
6210
                'parent_item_id' => $row['parent_item_id'],
6211
                'previous_item_id' => $row['previous_item_id'],
6212
                'next_item_id' => $row['next_item_id'],
6213
                'max_score' => $row['max_score'],
6214
                'min_score' => $row['min_score'],
6215
                'mastery_score' => $row['mastery_score'],
6216
                'prerequisite' => $row['prerequisite'],
6217
                'display_order' => $row['display_order'],
6218
                'audio' => $row['audio'],
6219
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6220
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6221
            ];
6222
        }
6223
6224
        $this->tree_array($arrLP);
6225
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6226
        unset($this->arrMenu);
6227
        $default_data = null;
6228
        $default_content = null;
6229
        $elements = [];
6230
        $return_audio = null;
6231
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6232
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6233
6234
        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...
6235
            $title = $arrLP[$i]['title'];
6236
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6237
6238
            // Link for the documents
6239
            if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6240
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6241
                $title_cut = Display::url(
6242
                    $title_cut,
6243
                    $url,
6244
                    [
6245
                        'class' => 'ajax moved',
6246
                        'data-title' => $title,
6247
                        'title' => $title,
6248
                    ]
6249
                );
6250
            }
6251
6252
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6253
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6254
                Session::write('pathItem', $arrLP[$i]['path']);
6255
            }
6256
6257
            if (($i % 2) == 0) {
6258
                $oddClass = 'row_odd';
6259
            } else {
6260
                $oddClass = 'row_even';
6261
            }
6262
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6263
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6264
6265
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6266
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6267
            } else {
6268
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6269
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6270
                } else {
6271
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6272
                        $icon = Display::return_icon('certificate.png');
6273
                    } else {
6274
                        $icon = Display::return_icon('folder_document.png');
6275
                    }
6276
                }
6277
            }
6278
6279
            // The audio column.
6280
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6281
            $audio = '';
6282
            if (!$update_audio || $update_audio != 'true') {
6283
                if (empty($arrLP[$i]['audio'])) {
6284
                    $audio .= '';
6285
                }
6286
            } else {
6287
                $types = self::getChapterTypes();
6288
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6289
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6290
                    if (!empty($arrLP[$i]['audio'])) {
6291
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6292
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6293
                    }
6294
                }
6295
            }
6296
6297
            $return_audio .= Display::span($icon.' '.$title).
6298
                Display::tag(
6299
                    'td',
6300
                    $audio,
6301
                    ['style' => '']
6302
                );
6303
            $return_audio .= '</td>';
6304
            $move_icon = '';
6305
            $move_item_icon = '';
6306
            $edit_icon = '';
6307
            $delete_icon = '';
6308
            $audio_icon = '';
6309
            $prerequisities_icon = '';
6310
            $forumIcon = '';
6311
            $previewIcon = '';
6312
            $pluginCalendarIcon = '';
6313
6314
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6315
            $plugin = null;
6316
            if ($pluginCalendar) {
6317
                $plugin = LearningCalendarPlugin::create();
6318
            }
6319
6320
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6321
6322
            if ($is_allowed_to_edit) {
6323
                if (!$update_audio || $update_audio != 'true') {
6324
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6325
                        $move_icon .= '<a class="moved" href="#">';
6326
                        $move_icon .= Display::return_icon(
6327
                            'move_everywhere.png',
6328
                            get_lang('Move'),
6329
                            [],
6330
                            ICON_SIZE_TINY
6331
                        );
6332
                        $move_icon .= '</a>';
6333
                    }
6334
                }
6335
6336
                // No edit for this item types
6337
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6338
                    if ($arrLP[$i]['item_type'] != 'dir') {
6339
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6340
                        $edit_icon .= Display::return_icon(
6341
                            'edit.png',
6342
                            get_lang('LearnpathEditModule'),
6343
                            [],
6344
                            ICON_SIZE_TINY
6345
                        );
6346
                        $edit_icon .= '</a>';
6347
6348
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6349
                            $forumThread = null;
6350
                            if (isset($this->items[$arrLP[$i]['id']])) {
6351
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6352
                                    $this->course_int_id,
6353
                                    $this->lp_session_id
6354
                                );
6355
                            }
6356
                            if ($forumThread) {
6357
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6358
                                        'action' => 'dissociate_forum',
6359
                                        'id' => $arrLP[$i]['id'],
6360
                                        'lp_id' => $this->lp_id,
6361
                                    ]);
6362
                                $forumIcon = Display::url(
6363
                                    Display::return_icon(
6364
                                        'forum.png',
6365
                                        get_lang('DissociateForumToLPItem'),
6366
                                        [],
6367
                                        ICON_SIZE_TINY
6368
                                    ),
6369
                                    $forumIconUrl,
6370
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6371
                                );
6372
                            } else {
6373
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6374
                                        'action' => 'create_forum',
6375
                                        'id' => $arrLP[$i]['id'],
6376
                                        'lp_id' => $this->lp_id,
6377
                                    ]);
6378
                                $forumIcon = Display::url(
6379
                                    Display::return_icon(
6380
                                        'forum.png',
6381
                                        get_lang('AssociateForumToLPItem'),
6382
                                        [],
6383
                                        ICON_SIZE_TINY
6384
                                    ),
6385
                                    $forumIconUrl,
6386
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6387
                                );
6388
                            }
6389
                        }
6390
                    } else {
6391
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'&path_item='.$arrLP[$i]['path'].'" class="btn btn-default">';
6392
                        $edit_icon .= Display::return_icon(
6393
                            'edit.png',
6394
                            get_lang('LearnpathEditModule'),
6395
                            [],
6396
                            ICON_SIZE_TINY
6397
                        );
6398
                        $edit_icon .= '</a>';
6399
                    }
6400
                } else {
6401
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6402
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6403
                        $edit_icon .= Display::return_icon(
6404
                            'edit.png',
6405
                            get_lang('Edit'),
6406
                            [],
6407
                            ICON_SIZE_TINY
6408
                        );
6409
                        $edit_icon .= '</a>';
6410
                    }
6411
                }
6412
6413
                if ($pluginCalendar) {
6414
                    $pluginLink = $pluginUrl.
6415
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6416
6417
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6418
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6419
                    if ($itemInfo && $itemInfo['value'] == 1) {
6420
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6421
                    }
6422
                    $pluginCalendarIcon = Display::url(
6423
                        $iconCalendar,
6424
                        $pluginLink,
6425
                        ['class' => 'btn btn-default']
6426
                    );
6427
                }
6428
6429
                $delete_icon .= ' <a 
6430
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6431
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6432
                    class="btn btn-default">';
6433
                $delete_icon .= Display::return_icon(
6434
                    'delete.png',
6435
                    get_lang('LearnpathDeleteModule'),
6436
                    [],
6437
                    ICON_SIZE_TINY
6438
                );
6439
                $delete_icon .= '</a>';
6440
6441
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6442
                $previewImage = Display::return_icon(
6443
                    'preview_view.png',
6444
                    get_lang('Preview'),
6445
                    [],
6446
                    ICON_SIZE_TINY
6447
                );
6448
6449
                switch ($arrLP[$i]['item_type']) {
6450
                    case TOOL_DOCUMENT:
6451
                    case TOOL_LP_FINAL_ITEM:
6452
                    case TOOL_READOUT_TEXT:
6453
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6454
                        $previewIcon = Display::url(
6455
                            $previewImage,
6456
                            $urlPreviewLink,
6457
                            [
6458
                                'target' => '_blank',
6459
                                'class' => 'btn btn-default',
6460
                                'data-title' => $arrLP[$i]['title'],
6461
                                'title' => $arrLP[$i]['title'],
6462
                            ]
6463
                        );
6464
                        break;
6465
                    case TOOL_THREAD:
6466
                    case TOOL_FORUM:
6467
                    case TOOL_QUIZ:
6468
                    case TOOL_STUDENTPUBLICATION:
6469
                    case TOOL_LP_FINAL_ITEM:
6470
                    case TOOL_LINK:
6471
                        //$target = '';
6472
                        //$class = 'btn btn-default ajax';
6473
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6474
                        $class = 'btn btn-default';
6475
                        $target = '_blank';
6476
                        //}
6477
6478
                        $link = self::rl_get_resource_link_for_learnpath(
6479
                            $this->course_int_id,
6480
                            $this->lp_id,
6481
                            $arrLP[$i]['id'],
6482
                            0
6483
                        );
6484
                        $previewIcon = Display::url(
6485
                            $previewImage,
6486
                            $link,
6487
                            [
6488
                                'class' => $class,
6489
                                'data-title' => $arrLP[$i]['title'],
6490
                                'title' => $arrLP[$i]['title'],
6491
                                'target' => $target,
6492
                            ]
6493
                        );
6494
                        break;
6495
                    default:
6496
                        $previewIcon = Display::url(
6497
                            $previewImage,
6498
                            $url.'&action=view_item',
6499
                            ['class' => 'btn btn-default', 'target' => '_blank']
6500
                        );
6501
                        break;
6502
                }
6503
6504
                if ($arrLP[$i]['item_type'] != 'dir') {
6505
                    $prerequisities_icon = Display::url(
6506
                        Display::return_icon(
6507
                            'accept.png',
6508
                            get_lang('LearnpathPrerequisites'),
6509
                            [],
6510
                            ICON_SIZE_TINY
6511
                        ),
6512
                        $url.'&action=edit_item_prereq',
6513
                        ['class' => 'btn btn-default']
6514
                    );
6515
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6516
                        $move_item_icon = Display::url(
6517
                            Display::return_icon(
6518
                                'move.png',
6519
                                get_lang('Move'),
6520
                                [],
6521
                                ICON_SIZE_TINY
6522
                            ),
6523
                            $url.'&action=move_item',
6524
                            ['class' => 'btn btn-default']
6525
                        );
6526
                    }
6527
                    $audio_icon = Display::url(
6528
                        Display::return_icon(
6529
                            'audio.png',
6530
                            get_lang('UplUpload'),
6531
                            [],
6532
                            ICON_SIZE_TINY
6533
                        ),
6534
                        $url.'&action=add_audio',
6535
                        ['class' => 'btn btn-default']
6536
                    );
6537
                }
6538
            }
6539
            if ($update_audio != 'true') {
6540
                $row = $move_icon.' '.$icon.
6541
                    Display::span($title_cut).
6542
                    Display::tag(
6543
                        'div',
6544
                        "<div class=\"btn-group btn-group-xs\">
6545
                                    $previewIcon 
6546
                                    $audio 
6547
                                    $edit_icon 
6548
                                    $pluginCalendarIcon
6549
                                    $forumIcon 
6550
                                    $prerequisities_icon 
6551
                                    $move_item_icon 
6552
                                    $audio_icon 
6553
                                    $delete_icon
6554
                                </div>",
6555
                        ['class' => 'btn-toolbar button_actions']
6556
                    );
6557
            } else {
6558
                $row =
6559
                    Display::span($title.$icon).
6560
                    Display::span($audio, ['class' => 'button_actions']);
6561
            }
6562
6563
            $parent_id = $arrLP[$i]['parent_item_id'];
6564
            $default_data[$arrLP[$i]['id']] = $row;
6565
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6566
6567
            if (empty($parent_id)) {
6568
                $elements[$arrLP[$i]['id']]['data'] = $row;
6569
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6570
            } else {
6571
                $parent_arrays = [];
6572
                if ($arrLP[$i]['depth'] > 1) {
6573
                    //Getting list of parents
6574
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6575
                        foreach ($arrLP as $item) {
6576
                            if ($item['id'] == $parent_id) {
6577
                                if ($item['parent_item_id'] == 0) {
6578
                                    $parent_id = $item['id'];
6579
                                    break;
6580
                                } else {
6581
                                    $parent_id = $item['parent_item_id'];
6582
                                    if (empty($parent_arrays)) {
6583
                                        $parent_arrays[] = intval($item['id']);
6584
                                    }
6585
                                    $parent_arrays[] = $parent_id;
6586
                                    break;
6587
                                }
6588
                            }
6589
                        }
6590
                    }
6591
                }
6592
6593
                if (!empty($parent_arrays)) {
6594
                    $parent_arrays = array_reverse($parent_arrays);
6595
                    $val = '$elements';
6596
                    $x = 0;
6597
                    foreach ($parent_arrays as $item) {
6598
                        if ($x != count($parent_arrays) - 1) {
6599
                            $val .= '["'.$item.'"]["children"]';
6600
                        } else {
6601
                            $val .= '["'.$item.'"]["children"]';
6602
                        }
6603
                        $x++;
6604
                    }
6605
                    $val .= "";
6606
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6607
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6608
                } else {
6609
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6610
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6611
                }
6612
            }
6613
        }
6614
6615
        return [
6616
            'elements' => $elements,
6617
            'default_data' => $default_data,
6618
            'default_content' => $default_content,
6619
            'return_audio' => $return_audio,
6620
        ];
6621
    }
6622
6623
    /**
6624
     * @param string $updateAudio true/false strings
6625
     *
6626
     * @return string
6627
     */
6628
    public function returnLpItemList($updateAudio)
6629
    {
6630
        $result = $this->processBuildMenuElements($updateAudio);
6631
6632
        $html = self::print_recursive(
6633
            $result['elements'],
6634
            $result['default_data'],
6635
            $result['default_content']
6636
        );
6637
6638
        if (!empty($html)) {
6639
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6640
        }
6641
6642
        return $html;
6643
    }
6644
6645
    /**
6646
     * @param string $update_audio
6647
     * @param bool   $drop_element_here
6648
     *
6649
     * @return string
6650
     */
6651
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6652
    {
6653
        $return = '';
6654
        $result = $this->processBuildMenuElements($update_audio);
6655
6656
        $list = '<ul id="lp_item_list">';
6657
        $tree = $this->print_recursive(
6658
            $result['elements'],
6659
            $result['default_data'],
6660
            $result['default_content']
6661
        );
6662
6663
        if (!empty($tree)) {
6664
            $list .= $tree;
6665
        } else {
6666
            if ($drop_element_here) {
6667
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6668
            }
6669
        }
6670
        $list .= '</ul>';
6671
6672
        $return .= Display::panelCollapse(
6673
            $this->name,
6674
            $list,
6675
            'scorm-list',
6676
            null,
6677
            'scorm-list-accordion',
6678
            'scorm-list-collapse'
6679
        );
6680
6681
        if ($update_audio === 'true') {
6682
            $return = $result['return_audio'];
6683
        }
6684
6685
        return $return;
6686
    }
6687
6688
    /**
6689
     * @param array $elements
6690
     * @param array $default_data
6691
     * @param array $default_content
6692
     *
6693
     * @return string
6694
     */
6695
    public function print_recursive($elements, $default_data, $default_content)
6696
    {
6697
        $return = '';
6698
        foreach ($elements as $key => $item) {
6699
            if (isset($item['load_data']) || empty($item['data'])) {
6700
                $item['data'] = $default_data[$item['load_data']];
6701
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6702
            }
6703
            $sub_list = '';
6704
            if (isset($item['type']) && $item['type'] == 'dir') {
6705
                // empty value
6706
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6707
            }
6708
            if (empty($item['children'])) {
6709
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6710
                $active = null;
6711
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6712
                    $active = 'active';
6713
                }
6714
                $return .= Display::tag(
6715
                    'li',
6716
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6717
                    ['id' => $key, 'class' => 'record li_container']
6718
                );
6719
            } else {
6720
                // Sections
6721
                $data = '';
6722
                if (isset($item['children'])) {
6723
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6724
                }
6725
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6726
                $return .= Display::tag(
6727
                    'li',
6728
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6729
                    ['id' => $key, 'class' => 'record li_container']
6730
                );
6731
            }
6732
        }
6733
6734
        return $return;
6735
    }
6736
6737
    /**
6738
     * This function builds the action menu.
6739
     *
6740
     * @param bool $returnContent          Optional
6741
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6742
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6743
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6744
     *
6745
     * @return string
6746
     */
6747
    public function build_action_menu(
6748
        $returnContent = false,
6749
        $showRequirementButtons = true,
6750
        $isConfigPage = false,
6751
        $allowExpand = true
6752
    ) {
6753
        $actionsLeft = '';
6754
        $actionsRight = '';
6755
        $actionsLeft .= Display::url(
6756
            Display::return_icon(
6757
                'preview_view.png',
6758
                get_lang('Preview'),
6759
                '',
6760
                ICON_SIZE_MEDIUM
6761
            ),
6762
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6763
                'action' => 'view',
6764
                'lp_id' => $this->lp_id,
6765
                'isStudentView' => 'true',
6766
            ])
6767
        );
6768
6769
        $actionsLeft .= Display::url(
6770
            Display::return_icon(
6771
                'upload_audio.png',
6772
                get_lang('UpdateAllAudioFragments'),
6773
                '',
6774
                ICON_SIZE_MEDIUM
6775
            ),
6776
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6777
                'action' => 'admin_view',
6778
                'lp_id' => $this->lp_id,
6779
                'updateaudio' => 'true',
6780
            ])
6781
        );
6782
6783
        if (!$isConfigPage) {
6784
            $actionsLeft .= Display::url(
6785
                Display::return_icon(
6786
                    'settings.png',
6787
                    get_lang('CourseSettings'),
6788
                    '',
6789
                    ICON_SIZE_MEDIUM
6790
                ),
6791
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6792
                    'action' => 'edit',
6793
                    'lp_id' => $this->lp_id,
6794
                ])
6795
            );
6796
        } else {
6797
            $actionsLeft .= Display::url(
6798
                Display::return_icon(
6799
                    'edit.png',
6800
                    get_lang('Edit'),
6801
                    '',
6802
                    ICON_SIZE_MEDIUM
6803
                ),
6804
                'lp_controller.php?'.http_build_query([
6805
                    'action' => 'build',
6806
                    'lp_id' => $this->lp_id,
6807
                ]).'&'.api_get_cidreq()
6808
            );
6809
        }
6810
6811
        if ($allowExpand) {
6812
            $actionsLeft .= Display::url(
6813
                Display::return_icon(
6814
                    'expand.png',
6815
                    get_lang('Expand'),
6816
                    ['id' => 'expand'],
6817
                    ICON_SIZE_MEDIUM
6818
                ).
6819
                Display::return_icon(
6820
                    'contract.png',
6821
                    get_lang('Collapse'),
6822
                    ['id' => 'contract', 'class' => 'hide'],
6823
                    ICON_SIZE_MEDIUM
6824
                ),
6825
                '#',
6826
                ['role' => 'button', 'id' => 'hide_bar_template']
6827
            );
6828
        }
6829
6830
        if ($showRequirementButtons) {
6831
            $buttons = [
6832
                [
6833
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6834
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6835
                        'action' => 'set_previous_step_as_prerequisite',
6836
                        'lp_id' => $this->lp_id,
6837
                    ]),
6838
                ],
6839
                [
6840
                    'title' => get_lang('ClearAllPrerequisites'),
6841
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6842
                        'action' => 'clear_prerequisites',
6843
                        'lp_id' => $this->lp_id,
6844
                    ]),
6845
                ],
6846
            ];
6847
            $actionsRight = Display::groupButtonWithDropDown(
6848
                get_lang('PrerequisitesOptions'),
6849
                $buttons,
6850
                true
6851
            );
6852
        }
6853
6854
        $toolbar = Display::toolbarAction(
6855
            'actions-lp-controller',
6856
            [$actionsLeft, $actionsRight]
6857
        );
6858
6859
        if ($returnContent) {
6860
            return $toolbar;
6861
        }
6862
6863
        echo $toolbar;
6864
    }
6865
6866
    /**
6867
     * Creates the default learning path folder.
6868
     *
6869
     * @param array $course
6870
     * @param int   $creatorId
6871
     *
6872
     * @return bool
6873
     */
6874
    public static function generate_learning_path_folder($course, $creatorId = 0)
6875
    {
6876
        // Creating learning_path folder
6877
        $dir = '/learning_path';
6878
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6879
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6880
6881
        $folder = false;
6882
        //if (!is_dir($filepath.'/'.$dir)) {
6883
        $folderData = create_unexisting_directory(
6884
                $course,
6885
                $creatorId,
6886
                0,
6887
                null,
6888
                0,
6889
                $filepath,
6890
                $dir,
6891
                get_lang('LearningPaths'),
6892
                0
6893
            );
6894
        if (!empty($folderData)) {
6895
            $folder = true;
6896
        }
6897
        /*} else {
6898
            $folder = true;
6899
        }*/
6900
6901
        return $folder;
6902
    }
6903
6904
    /**
6905
     * @param array  $course
6906
     * @param string $lp_name
6907
     * @param int    $creatorId
6908
     *
6909
     * @return array
6910
     */
6911
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6912
    {
6913
        $filepath = '';
6914
        $dir = '/learning_path/';
6915
6916
        if (empty($lp_name)) {
6917
            $lp_name = $this->name;
6918
        }
6919
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6920
6921
        $folder = self::generate_learning_path_folder($course, $creatorId);
6922
6923
        // Limits title size
6924
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6925
        $dir = $dir.$title;
6926
6927
        // Creating LP folder
6928
        $documentId = null;
6929
        if ($folder) {
6930
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6931
            //if (!is_dir($filepath.'/'.$dir)) {
6932
            $folderData = create_unexisting_directory(
6933
                    $course,
6934
                    $creatorId,
6935
                    0,
6936
                    0,
6937
                    0,
6938
                    $filepath,
6939
                    $dir,
6940
                    $lp_name
6941
                );
6942
            if (!empty($folderData)) {
6943
                $folder = true;
6944
            }
6945
6946
            $documentId = $folderData->getIid();
6947
            /*} else {
6948
                $folder = true;
6949
            }*/
6950
6951
            $dir = $dir.'/';
6952
            if ($folder) {
6953
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6954
            }
6955
        }
6956
6957
        /*if (empty($documentId)) {
6958
            $dir = api_remove_trailing_slash($dir);
6959
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6960
        }*/
6961
6962
        $array = [
6963
            'dir' => $dir,
6964
            'filepath' => $filepath,
6965
            'folder' => $folder,
6966
            'id' => $documentId,
6967
        ];
6968
6969
        return $array;
6970
    }
6971
6972
    /**
6973
     * Create a new document //still needs some finetuning.
6974
     *
6975
     * @param array  $courseInfo
6976
     * @param string $content
6977
     * @param string $title
6978
     * @param string $extension
6979
     * @param int    $parentId
6980
     * @param int    $creatorId  creator id
6981
     *
6982
     * @return int
6983
     */
6984
    public function create_document(
6985
        $courseInfo,
6986
        $content = '',
6987
        $title = '',
6988
        $extension = 'html',
6989
        $parentId = 0,
6990
        $creatorId = 0
6991
    ) {
6992
        if (!empty($courseInfo)) {
6993
            $course_id = $courseInfo['real_id'];
6994
        } else {
6995
            $course_id = api_get_course_int_id();
6996
        }
6997
6998
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6999
        $sessionId = api_get_session_id();
7000
7001
        // Generates folder
7002
        $result = $this->generate_lp_folder($courseInfo);
7003
        $dir = $result['dir'];
7004
7005
        if (empty($parentId) || $parentId == '/') {
7006
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7007
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7008
7009
            if ($parentId === '/') {
7010
                $dir = '/';
7011
            }
7012
7013
            // Please, do not modify this dirname formatting.
7014
            if (strstr($dir, '..')) {
7015
                $dir = '/';
7016
            }
7017
7018
            if (!empty($dir[0]) && $dir[0] == '.') {
7019
                $dir = substr($dir, 1);
7020
            }
7021
            if (!empty($dir[0]) && $dir[0] != '/') {
7022
                $dir = '/'.$dir;
7023
            }
7024
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7025
                $dir .= '/';
7026
            }
7027
        } else {
7028
            $parentInfo = DocumentManager::get_document_data_by_id(
7029
                $parentId,
7030
                $courseInfo['code']
7031
            );
7032
            if (!empty($parentInfo)) {
7033
                $dir = $parentInfo['path'].'/';
7034
            }
7035
        }
7036
7037
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7038
        if (!is_dir($filepath)) {
7039
            $dir = '/';
7040
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7041
        }
7042
7043
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7044
        // is already escaped twice when it gets here.
7045
7046
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7047
        if (!empty($title)) {
7048
            $title = api_replace_dangerous_char(stripslashes($title));
7049
        } else {
7050
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7051
        }
7052
7053
        $title = disable_dangerous_file($title);
7054
        $filename = $title;
7055
        $content = !empty($content) ? $content : $_POST['content_lp'];
7056
        $tmp_filename = $filename;
7057
7058
        $i = 0;
7059
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7060
            $tmp_filename = $filename.'_'.++$i;
7061
        }
7062
7063
        $filename = $tmp_filename.'.'.$extension;
7064
        if ($extension == 'html') {
7065
            $content = stripslashes($content);
7066
            $content = str_replace(
7067
                api_get_path(WEB_COURSE_PATH),
7068
                api_get_path(REL_PATH).'courses/',
7069
                $content
7070
            );
7071
7072
            // Change the path of mp3 to absolute.
7073
7074
            // The first regexp deals with :// urls.
7075
            $content = preg_replace(
7076
                "|(flashvars=\"file=)([^:/]+)/|",
7077
                "$1".api_get_path(
7078
                    REL_COURSE_PATH
7079
                ).$courseInfo['path'].'/document/',
7080
                $content
7081
            );
7082
            // The second regexp deals with audio/ urls.
7083
            $content = preg_replace(
7084
                "|(flashvars=\"file=)([^/]+)/|",
7085
                "$1".api_get_path(
7086
                    REL_COURSE_PATH
7087
                ).$courseInfo['path'].'/document/$2/',
7088
                $content
7089
            );
7090
            // For flv player: To prevent edition problem with firefox,
7091
            // we have to use a strange tip (don't blame me please).
7092
            $content = str_replace(
7093
                '</body>',
7094
                '<style type="text/css">body{}</style></body>',
7095
                $content
7096
            );
7097
        }
7098
7099
        if (!file_exists($filepath.$filename)) {
7100
            if ($fp = @fopen($filepath.$filename, 'w')) {
7101
                fputs($fp, $content);
7102
                fclose($fp);
7103
7104
                $file_size = filesize($filepath.$filename);
7105
                $save_file_path = $dir.$filename;
7106
7107
                $document = DocumentManager::addDocument(
7108
                    $courseInfo,
7109
                    $save_file_path,
7110
                    'file',
7111
                    $file_size,
7112
                    $tmp_filename,
7113
                    '',
7114
                    0, //readonly
7115
                    true,
7116
                    null,
7117
                    $sessionId,
7118
                    $creatorId
7119
                );
7120
7121
                $document_id = $document->getId();
7122
7123
                if ($document_id) {
7124
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7125
                    $new_title = $originalTitle;
7126
7127
                    if ($new_comment || $new_title) {
7128
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7129
                        $ct = '';
7130
                        if ($new_comment) {
7131
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7132
                        }
7133
                        if ($new_title) {
7134
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7135
                        }
7136
7137
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7138
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7139
                        Database::query($sql);
7140
                    }
7141
                }
7142
7143
                return $document_id;
7144
            }
7145
        }
7146
    }
7147
7148
    /**
7149
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7150
     *
7151
     * @param array $_course array
7152
     */
7153
    public function edit_document($_course)
7154
    {
7155
        $course_id = api_get_course_int_id();
7156
        $urlAppend = api_get_configuration_value('url_append');
7157
        // Please, do not modify this dirname formatting.
7158
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7159
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7160
7161
        if (strstr($dir, '..')) {
7162
            $dir = '/';
7163
        }
7164
7165
        if (isset($dir[0]) && $dir[0] == '.') {
7166
            $dir = substr($dir, 1);
7167
        }
7168
7169
        if (isset($dir[0]) && $dir[0] != '/') {
7170
            $dir = '/'.$dir;
7171
        }
7172
7173
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7174
            $dir .= '/';
7175
        }
7176
7177
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7178
        if (!is_dir($filepath)) {
7179
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7180
        }
7181
7182
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7183
7184
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7185
            $document_id = (int) $_POST['path'];
7186
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7187
            if (empty($documentInfo)) {
7188
                // Try with iid
7189
                $table = Database::get_course_table(TABLE_DOCUMENT);
7190
                $sql = "SELECT id, path FROM $table 
7191
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7192
                $res_doc = Database::query($sql);
7193
                $row = Database::fetch_array($res_doc);
7194
                if ($row) {
7195
                    $document_id = $row['id'];
7196
                    $documentPath = $row['path'];
7197
                }
7198
            } else {
7199
                $documentPath = $documentInfo['path'];
7200
            }
7201
7202
            $content = stripslashes($_POST['content_lp']);
7203
            $file = $filepath.$documentPath;
7204
7205
            if (!file_exists($file)) {
7206
                return false;
7207
            }
7208
7209
            if ($fp = @fopen($file, 'w')) {
7210
                $content = str_replace(
7211
                    api_get_path(WEB_COURSE_PATH),
7212
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7213
                    $content
7214
                );
7215
                // Change the path of mp3 to absolute.
7216
                // The first regexp deals with :// urls.
7217
                $content = preg_replace(
7218
                    "|(flashvars=\"file=)([^:/]+)/|",
7219
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7220
                    $content
7221
                );
7222
                // The second regexp deals with audio/ urls.
7223
                $content = preg_replace(
7224
                    "|(flashvars=\"file=)([^:/]+)/|",
7225
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7226
                    $content
7227
                );
7228
                fputs($fp, $content);
7229
                fclose($fp);
7230
7231
                $sql = "UPDATE $table_doc SET
7232
                            title='".Database::escape_string($_POST['title'])."'
7233
                        WHERE c_id = $course_id AND id = ".$document_id;
7234
                Database::query($sql);
7235
            }
7236
        }
7237
    }
7238
7239
    /**
7240
     * Displays the selected item, with a panel for manipulating the item.
7241
     *
7242
     * @param int    $item_id
7243
     * @param string $msg
7244
     * @param bool   $show_actions
7245
     *
7246
     * @return string
7247
     */
7248
    public function display_item($item_id, $msg = null, $show_actions = true)
7249
    {
7250
        $course_id = api_get_course_int_id();
7251
        $return = '';
7252
        if (is_numeric($item_id)) {
7253
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7254
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7255
                    WHERE lp.iid = ".intval($item_id);
7256
            $result = Database::query($sql);
7257
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7258
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7259
7260
                // Prevents wrong parent selection for document, see Bug#1251.
7261
                if ($row['item_type'] != 'dir') {
7262
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7263
                }
7264
7265
                if ($show_actions) {
7266
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7267
                }
7268
                $return .= '<div style="padding:10px;">';
7269
7270
                if ($msg != '') {
7271
                    $return .= $msg;
7272
                }
7273
7274
                $return .= '<h3>'.$row['title'].'</h3>';
7275
7276
                switch ($row['item_type']) {
7277
                    case TOOL_THREAD:
7278
                        $link = $this->rl_get_resource_link_for_learnpath(
7279
                            $course_id,
7280
                            $row['lp_id'],
7281
                            $item_id,
7282
                            0
7283
                        );
7284
                        $return .= Display::url(
7285
                            get_lang('GoToThread'),
7286
                            $link,
7287
                            ['class' => 'btn btn-primary']
7288
                        );
7289
                        break;
7290
                    case TOOL_FORUM:
7291
                        $return .= Display::url(
7292
                            get_lang('GoToForum'),
7293
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7294
                            ['class' => 'btn btn-primary']
7295
                        );
7296
                        break;
7297
                    case TOOL_QUIZ:
7298
                        if (!empty($row['path'])) {
7299
                            $exercise = new Exercise();
7300
                            $exercise->read($row['path']);
7301
                            $return .= $exercise->description.'<br />';
7302
                            $return .= Display::url(
7303
                                get_lang('GoToExercise'),
7304
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7305
                                ['class' => 'btn btn-primary']
7306
                            );
7307
                        }
7308
                        break;
7309
                    case TOOL_LP_FINAL_ITEM:
7310
                        $return .= $this->getSavedFinalItem();
7311
                        break;
7312
                    case TOOL_DOCUMENT:
7313
                    case TOOL_READOUT_TEXT:
7314
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7315
                        $sql_doc = "SELECT path FROM ".$tbl_doc."
7316
                                    WHERE c_id = ".$course_id." AND iid = ".intval($row['path']);
7317
                        $result = Database::query($sql_doc);
7318
                        $path_file = Database::result($result, 0, 0);
7319
                        $path_parts = pathinfo($path_file);
7320
                        // TODO: Correct the following naive comparisons.
7321
                        if (in_array($path_parts['extension'], [
7322
                            'html',
7323
                            'txt',
7324
                            'png',
7325
                            'jpg',
7326
                            'JPG',
7327
                            'jpeg',
7328
                            'JPEG',
7329
                            'gif',
7330
                            'swf',
7331
                            'pdf',
7332
                            'htm',
7333
                        ])) {
7334
                            $return .= $this->display_document($row['path'], true, true);
7335
                        }
7336
                        break;
7337
                    case TOOL_HOTPOTATOES:
7338
                        $return .= $this->display_document($row['path'], false, true);
7339
                        break;
7340
                }
7341
                $return .= '</div>';
7342
            }
7343
        }
7344
7345
        return $return;
7346
    }
7347
7348
    /**
7349
     * Shows the needed forms for editing a specific item.
7350
     *
7351
     * @param int $item_id
7352
     *
7353
     * @throws Exception
7354
     * @throws HTML_QuickForm_Error
7355
     *
7356
     * @return string
7357
     */
7358
    public function display_edit_item($item_id)
7359
    {
7360
        $course_id = api_get_course_int_id();
7361
        $return = '';
7362
        $item_id = (int) $item_id;
7363
7364
        if (empty($item_id)) {
7365
            return '';
7366
        }
7367
7368
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7369
        $sql = "SELECT * FROM $tbl_lp_item
7370
                WHERE iid = ".$item_id;
7371
        $res = Database::query($sql);
7372
        $row = Database::fetch_array($res);
7373
        switch ($row['item_type']) {
7374
            case 'dir':
7375
            case 'asset':
7376
            case 'sco':
7377
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7378
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7379
                    $return .= $this->display_item_form(
7380
                        $row['item_type'],
7381
                        get_lang('EditCurrentChapter').' :',
7382
                        'edit',
7383
                        $item_id,
7384
                        $row
7385
                    );
7386
                } else {
7387
                    $return .= $this->display_item_form(
7388
                        $row['item_type'],
7389
                        get_lang('EditCurrentChapter').' :',
7390
                        'edit_item',
7391
                        $item_id,
7392
                        $row
7393
                    );
7394
                }
7395
                break;
7396
            case TOOL_DOCUMENT:
7397
            case TOOL_READOUT_TEXT:
7398
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7399
                $sql = "SELECT lp.*, doc.path as dir
7400
                        FROM $tbl_lp_item as lp
7401
                        LEFT JOIN $tbl_doc as doc
7402
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7403
                        WHERE
7404
                            doc.c_id = $course_id AND
7405
                            lp.iid = ".$item_id;
7406
                $res_step = Database::query($sql);
7407
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7408
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7409
7410
                if ($row['item_type'] === TOOL_DOCUMENT) {
7411
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7412
                }
7413
7414
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7415
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7416
                }
7417
                break;
7418
            case TOOL_LINK:
7419
                $link_id = (string) $row['path'];
7420
                if (ctype_digit($link_id)) {
7421
                    $tbl_link = Database::get_course_table(TABLE_LINK);
7422
                    $sql_select = 'SELECT url FROM '.$tbl_link.'
7423
                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
7424
                    $res_link = Database::query($sql_select);
7425
                    $row_link = Database::fetch_array($res_link);
7426
                    if (is_array($row_link)) {
7427
                        $row['url'] = $row_link['url'];
7428
                    }
7429
                }
7430
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7431
                $return .= $this->display_link_form('edit', $item_id, $row);
7432
                break;
7433
            case TOOL_LP_FINAL_ITEM:
7434
                Session::write('finalItem', true);
7435
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7436
                $sql = "SELECT lp.*, doc.path as dir
7437
                        FROM $tbl_lp_item as lp
7438
                        LEFT JOIN $tbl_doc as doc
7439
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7440
                        WHERE
7441
                            doc.c_id = $course_id AND
7442
                            lp.iid = ".$item_id;
7443
                $res_step = Database::query($sql);
7444
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7445
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7446
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7447
                break;
7448
            case TOOL_QUIZ:
7449
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7450
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7451
                break;
7452
            case TOOL_HOTPOTATOES:
7453
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7454
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7455
                break;
7456
            case TOOL_STUDENTPUBLICATION:
7457
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7458
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7459
                break;
7460
            case TOOL_FORUM:
7461
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7462
                $return .= $this->display_forum_form('edit', $item_id, $row);
7463
                break;
7464
            case TOOL_THREAD:
7465
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7466
                $return .= $this->display_thread_form('edit', $item_id, $row);
7467
                break;
7468
        }
7469
7470
        return $return;
7471
    }
7472
7473
    /**
7474
     * Function that displays a list with al the resources that
7475
     * could be added to the learning path.
7476
     *
7477
     * @throws Exception
7478
     * @throws HTML_QuickForm_Error
7479
     *
7480
     * @return bool
7481
     */
7482
    public function display_resources()
7483
    {
7484
        $course_code = api_get_course_id();
7485
7486
        // Get all the docs.
7487
        $documents = $this->get_documents(true);
7488
7489
        // Get all the exercises.
7490
        $exercises = $this->get_exercises();
7491
7492
        // Get all the links.
7493
        $links = $this->get_links();
7494
7495
        // Get all the student publications.
7496
        $works = $this->get_student_publications();
7497
7498
        // Get all the forums.
7499
        $forums = $this->get_forums(null, $course_code);
7500
7501
        // Get the final item form (see BT#11048) .
7502
        $finish = $this->getFinalItemForm();
7503
7504
        $headers = [
7505
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7506
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7507
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7508
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7509
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7510
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7511
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7512
        ];
7513
7514
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7515
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7516
        echo Display::tabs(
7517
            $headers,
7518
            [
7519
                $documents,
7520
                $exercises,
7521
                $links,
7522
                $works,
7523
                $forums,
7524
                $dir,
7525
                $finish,
7526
            ],
7527
            'resource_tab'
7528
        );
7529
7530
        return true;
7531
    }
7532
7533
    /**
7534
     * Returns the extension of a document.
7535
     *
7536
     * @param string $filename
7537
     *
7538
     * @return string Extension (part after the last dot)
7539
     */
7540
    public function get_extension($filename)
7541
    {
7542
        $explode = explode('.', $filename);
7543
7544
        return $explode[count($explode) - 1];
7545
    }
7546
7547
    /**
7548
     * Displays a document by id.
7549
     *
7550
     * @param int  $id
7551
     * @param bool $show_title
7552
     * @param bool $iframe
7553
     * @param bool $edit_link
7554
     *
7555
     * @return string
7556
     */
7557
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7558
    {
7559
        $_course = api_get_course_info();
7560
        $course_id = api_get_course_int_id();
7561
        $id = (int) $id;
7562
        $return = '';
7563
        $table = Database::get_course_table(TABLE_DOCUMENT);
7564
        $sql_doc = "SELECT * FROM $table
7565
                    WHERE c_id = $course_id AND iid = $id";
7566
        $res_doc = Database::query($sql_doc);
7567
        $row_doc = Database::fetch_array($res_doc);
7568
7569
        // TODO: Add a path filter.
7570
        if ($iframe) {
7571
            $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>';
7572
        } else {
7573
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7574
        }
7575
7576
        return $return;
7577
    }
7578
7579
    /**
7580
     * Return HTML form to add/edit a quiz.
7581
     *
7582
     * @param string $action     Action (add/edit)
7583
     * @param int    $id         Item ID if already exists
7584
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7585
     *
7586
     * @throws Exception
7587
     *
7588
     * @return string HTML form
7589
     */
7590
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7591
    {
7592
        $course_id = api_get_course_int_id();
7593
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7594
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7595
7596
        if ($id != 0 && is_array($extra_info)) {
7597
            $item_title = $extra_info['title'];
7598
            $item_description = $extra_info['description'];
7599
        } elseif (is_numeric($extra_info)) {
7600
            $sql = "SELECT title, description
7601
                    FROM $tbl_quiz
7602
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7603
7604
            $result = Database::query($sql);
7605
            $row = Database::fetch_array($result);
7606
            $item_title = $row['title'];
7607
            $item_description = $row['description'];
7608
        } else {
7609
            $item_title = '';
7610
            $item_description = '';
7611
        }
7612
        $item_title = Security::remove_XSS($item_title);
7613
        $item_description = Security::remove_XSS($item_description);
7614
7615
        if ($id != 0 && is_array($extra_info)) {
7616
            $parent = $extra_info['parent_item_id'];
7617
        } else {
7618
            $parent = 0;
7619
        }
7620
7621
        $sql = "SELECT * FROM $tbl_lp_item 
7622
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7623
7624
        $result = Database::query($sql);
7625
        $arrLP = [];
7626
        while ($row = Database::fetch_array($result)) {
7627
            $arrLP[] = [
7628
                'id' => $row['iid'],
7629
                'item_type' => $row['item_type'],
7630
                'title' => $row['title'],
7631
                'path' => $row['path'],
7632
                'description' => $row['description'],
7633
                'parent_item_id' => $row['parent_item_id'],
7634
                'previous_item_id' => $row['previous_item_id'],
7635
                'next_item_id' => $row['next_item_id'],
7636
                'display_order' => $row['display_order'],
7637
                'max_score' => $row['max_score'],
7638
                'min_score' => $row['min_score'],
7639
                'mastery_score' => $row['mastery_score'],
7640
                'prerequisite' => $row['prerequisite'],
7641
                'max_time_allowed' => $row['max_time_allowed'],
7642
            ];
7643
        }
7644
7645
        $this->tree_array($arrLP);
7646
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7647
        unset($this->arrMenu);
7648
7649
        $form = new FormValidator(
7650
            'quiz_form',
7651
            'POST',
7652
            $this->getCurrentBuildingModeURL()
7653
        );
7654
        $defaults = [];
7655
7656
        if ($action == 'add') {
7657
            $legend = get_lang('CreateTheExercise');
7658
        } elseif ($action == 'move') {
7659
            $legend = get_lang('MoveTheCurrentExercise');
7660
        } else {
7661
            $legend = get_lang('EditCurrentExecice');
7662
        }
7663
7664
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7665
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7666
        }
7667
7668
        $form->addHeader($legend);
7669
7670
        if ($action != 'move') {
7671
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7672
            $defaults['title'] = $item_title;
7673
        }
7674
7675
        // Select for Parent item, root or chapter
7676
        $selectParent = $form->addSelect(
7677
            'parent',
7678
            get_lang('Parent'),
7679
            [],
7680
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7681
        );
7682
        $selectParent->addOption($this->name, 0);
7683
7684
        $arrHide = [
7685
            $id,
7686
        ];
7687
        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...
7688
            if ($action != 'add') {
7689
                if (
7690
                    ($arrLP[$i]['item_type'] == 'dir') &&
7691
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7692
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7693
                ) {
7694
                    $selectParent->addOption(
7695
                        $arrLP[$i]['title'],
7696
                        $arrLP[$i]['id'],
7697
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7698
                    );
7699
7700
                    if ($parent == $arrLP[$i]['id']) {
7701
                        $selectParent->setSelected($arrLP[$i]['id']);
7702
                    }
7703
                } else {
7704
                    $arrHide[] = $arrLP[$i]['id'];
7705
                }
7706
            } else {
7707
                if ($arrLP[$i]['item_type'] == 'dir') {
7708
                    $selectParent->addOption(
7709
                        $arrLP[$i]['title'],
7710
                        $arrLP[$i]['id'],
7711
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7712
                    );
7713
7714
                    if ($parent == $arrLP[$i]['id']) {
7715
                        $selectParent->setSelected($arrLP[$i]['id']);
7716
                    }
7717
                }
7718
            }
7719
        }
7720
        if (is_array($arrLP)) {
7721
            reset($arrLP);
7722
        }
7723
7724
        $selectPrevious = $form->addSelect(
7725
            'previous',
7726
            get_lang('Position'),
7727
            [],
7728
            ['id' => 'previous']
7729
        );
7730
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7731
7732
        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...
7733
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7734
                $arrLP[$i]['id'] != $id
7735
            ) {
7736
                $selectPrevious->addOption(
7737
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7738
                    $arrLP[$i]['id']
7739
                );
7740
7741
                if (is_array($extra_info)) {
7742
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7743
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7744
                    }
7745
                } elseif ($action == 'add') {
7746
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7747
                }
7748
            }
7749
        }
7750
7751
        if ($action != 'move') {
7752
            $arrHide = [];
7753
            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...
7754
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7755
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7756
                }
7757
            }
7758
        }
7759
7760
        if ($action == 'add') {
7761
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7762
        } else {
7763
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7764
        }
7765
7766
        if ($action == 'move') {
7767
            $form->addHidden('title', $item_title);
7768
            $form->addHidden('description', $item_description);
7769
        }
7770
7771
        if (is_numeric($extra_info)) {
7772
            $form->addHidden('path', $extra_info);
7773
        } elseif (is_array($extra_info)) {
7774
            $form->addHidden('path', $extra_info['path']);
7775
        }
7776
7777
        $form->addHidden('type', TOOL_QUIZ);
7778
        $form->addHidden('post_time', time());
7779
        $form->setDefaults($defaults);
7780
7781
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7782
    }
7783
7784
    /**
7785
     * Addition of Hotpotatoes tests.
7786
     *
7787
     * @param string $action
7788
     * @param int    $id         Internal ID of the item
7789
     * @param string $extra_info
7790
     *
7791
     * @return string HTML structure to display the hotpotatoes addition formular
7792
     */
7793
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7794
    {
7795
        $course_id = api_get_course_int_id();
7796
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7797
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7798
7799
        if ($id != 0 && is_array($extra_info)) {
7800
            $item_title = stripslashes($extra_info['title']);
7801
            $item_description = stripslashes($extra_info['description']);
7802
        } elseif (is_numeric($extra_info)) {
7803
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7804
7805
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7806
                    WHERE
7807
                        c_id = ".$course_id." AND
7808
                        path LIKE '".$uploadPath."/%/%htm%' AND
7809
                        iid = ".(int) $extra_info."
7810
                    ORDER BY iid ASC";
7811
7812
            $res_hot = Database::query($sql);
7813
            $row = Database::fetch_array($res_hot);
7814
7815
            $item_title = $row['title'];
7816
            $item_description = $row['description'];
7817
7818
            if (!empty($row['comment'])) {
7819
                $item_title = $row['comment'];
7820
            }
7821
        } else {
7822
            $item_title = '';
7823
            $item_description = '';
7824
        }
7825
7826
        if ($id != 0 && is_array($extra_info)) {
7827
            $parent = $extra_info['parent_item_id'];
7828
        } else {
7829
            $parent = 0;
7830
        }
7831
7832
        $sql = "SELECT * FROM $tbl_lp_item
7833
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7834
        $result = Database::query($sql);
7835
        $arrLP = [];
7836
        while ($row = Database::fetch_array($result)) {
7837
            $arrLP[] = [
7838
                'id' => $row['id'],
7839
                'item_type' => $row['item_type'],
7840
                'title' => $row['title'],
7841
                'path' => $row['path'],
7842
                'description' => $row['description'],
7843
                'parent_item_id' => $row['parent_item_id'],
7844
                'previous_item_id' => $row['previous_item_id'],
7845
                'next_item_id' => $row['next_item_id'],
7846
                'display_order' => $row['display_order'],
7847
                'max_score' => $row['max_score'],
7848
                'min_score' => $row['min_score'],
7849
                'mastery_score' => $row['mastery_score'],
7850
                'prerequisite' => $row['prerequisite'],
7851
                'max_time_allowed' => $row['max_time_allowed'],
7852
            ];
7853
        }
7854
7855
        $legend = '<legend>';
7856
        if ($action == 'add') {
7857
            $legend .= get_lang('CreateTheExercise');
7858
        } elseif ($action == 'move') {
7859
            $legend .= get_lang('MoveTheCurrentExercise');
7860
        } else {
7861
            $legend .= get_lang('EditCurrentExecice');
7862
        }
7863
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7864
            $legend .= Display:: return_message(
7865
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7866
            );
7867
        }
7868
        $legend .= '</legend>';
7869
7870
        $return = '<form method="POST">';
7871
        $return .= $legend;
7872
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7873
        $return .= '<tr>';
7874
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7875
        $return .= '<td class="input">';
7876
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7877
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7878
        $arrHide = [
7879
            $id,
7880
        ];
7881
7882
        if (count($arrLP) > 0) {
7883
            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...
7884
                if ($action != 'add') {
7885
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7886
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7887
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7888
                    ) {
7889
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7890
                    } else {
7891
                        $arrHide[] = $arrLP[$i]['id'];
7892
                    }
7893
                } else {
7894
                    if ($arrLP[$i]['item_type'] == 'dir') {
7895
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7896
                    }
7897
                }
7898
            }
7899
            reset($arrLP);
7900
        }
7901
7902
        $return .= '</select>';
7903
        $return .= '</td>';
7904
        $return .= '</tr>';
7905
        $return .= '<tr>';
7906
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7907
        $return .= '<td class="input">';
7908
        $return .= '<select id="previous" name="previous" size="1">';
7909
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7910
7911
        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...
7912
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7913
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7914
                    $selected = 'selected="selected" ';
7915
                } elseif ($action == 'add') {
7916
                    $selected = 'selected="selected" ';
7917
                } else {
7918
                    $selected = '';
7919
                }
7920
7921
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7922
            }
7923
        }
7924
7925
        $return .= '</select>';
7926
        $return .= '</td>';
7927
        $return .= '</tr>';
7928
7929
        if ($action != 'move') {
7930
            $return .= '<tr>';
7931
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7932
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7933
            $return .= '</tr>';
7934
            $id_prerequisite = 0;
7935
            if (is_array($arrLP) && count($arrLP) > 0) {
7936
                foreach ($arrLP as $key => $value) {
7937
                    if ($value['id'] == $id) {
7938
                        $id_prerequisite = $value['prerequisite'];
7939
                        break;
7940
                    }
7941
                }
7942
7943
                $arrHide = [];
7944
                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...
7945
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7946
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7947
                    }
7948
                }
7949
            }
7950
        }
7951
7952
        $return .= '<tr>';
7953
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7954
            get_lang('SaveHotpotatoes').'</button></td>';
7955
        $return .= '</tr>';
7956
        $return .= '</table>';
7957
7958
        if ($action == 'move') {
7959
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7960
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7961
        }
7962
7963
        if (is_numeric($extra_info)) {
7964
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7965
        } elseif (is_array($extra_info)) {
7966
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7967
        }
7968
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7969
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7970
        $return .= '</form>';
7971
7972
        return $return;
7973
    }
7974
7975
    /**
7976
     * Return the form to display the forum edit/add option.
7977
     *
7978
     * @param string $action
7979
     * @param int    $id         ID of the lp_item if already exists
7980
     * @param string $extra_info
7981
     *
7982
     * @throws Exception
7983
     *
7984
     * @return string HTML form
7985
     */
7986
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7987
    {
7988
        $course_id = api_get_course_int_id();
7989
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7990
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7991
7992
        if ($id != 0 && is_array($extra_info)) {
7993
            $item_title = stripslashes($extra_info['title']);
7994
        } elseif (is_numeric($extra_info)) {
7995
            $sql = "SELECT forum_title as title, forum_comment as comment
7996
                    FROM ".$tbl_forum."
7997
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
7998
7999
            $result = Database::query($sql);
8000
            $row = Database::fetch_array($result);
8001
8002
            $item_title = $row['title'];
8003
            $item_description = $row['comment'];
8004
        } else {
8005
            $item_title = '';
8006
            $item_description = '';
8007
        }
8008
8009
        if ($id != 0 && is_array($extra_info)) {
8010
            $parent = $extra_info['parent_item_id'];
8011
        } else {
8012
            $parent = 0;
8013
        }
8014
8015
        $sql = "SELECT * FROM $tbl_lp_item
8016
                WHERE
8017
                    c_id = $course_id AND
8018
                    lp_id = ".$this->lp_id;
8019
        $result = Database::query($sql);
8020
        $arrLP = [];
8021
        while ($row = Database::fetch_array($result)) {
8022
            $arrLP[] = [
8023
                'id' => $row['iid'],
8024
                'item_type' => $row['item_type'],
8025
                'title' => $row['title'],
8026
                'path' => $row['path'],
8027
                'description' => $row['description'],
8028
                'parent_item_id' => $row['parent_item_id'],
8029
                'previous_item_id' => $row['previous_item_id'],
8030
                'next_item_id' => $row['next_item_id'],
8031
                'display_order' => $row['display_order'],
8032
                'max_score' => $row['max_score'],
8033
                'min_score' => $row['min_score'],
8034
                'mastery_score' => $row['mastery_score'],
8035
                'prerequisite' => $row['prerequisite'],
8036
            ];
8037
        }
8038
8039
        $this->tree_array($arrLP);
8040
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8041
        unset($this->arrMenu);
8042
8043
        if ($action == 'add') {
8044
            $legend = get_lang('CreateTheForum');
8045
        } elseif ($action == 'move') {
8046
            $legend = get_lang('MoveTheCurrentForum');
8047
        } else {
8048
            $legend = get_lang('EditCurrentForum');
8049
        }
8050
8051
        $form = new FormValidator(
8052
            'forum_form',
8053
            'POST',
8054
            $this->getCurrentBuildingModeURL()
8055
        );
8056
        $defaults = [];
8057
8058
        $form->addHeader($legend);
8059
8060
        if ($action != 'move') {
8061
            $form->addText(
8062
                'title',
8063
                get_lang('Title'),
8064
                true,
8065
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8066
            );
8067
            $defaults['title'] = $item_title;
8068
        }
8069
8070
        $selectParent = $form->addSelect(
8071
            'parent',
8072
            get_lang('Parent'),
8073
            [],
8074
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8075
        );
8076
        $selectParent->addOption($this->name, 0);
8077
        $arrHide = [
8078
            $id,
8079
        ];
8080
        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...
8081
            if ($action != 'add') {
8082
                if ($arrLP[$i]['item_type'] == 'dir' &&
8083
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8084
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8085
                ) {
8086
                    $selectParent->addOption(
8087
                        $arrLP[$i]['title'],
8088
                        $arrLP[$i]['id'],
8089
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8090
                    );
8091
8092
                    if ($parent == $arrLP[$i]['id']) {
8093
                        $selectParent->setSelected($arrLP[$i]['id']);
8094
                    }
8095
                } else {
8096
                    $arrHide[] = $arrLP[$i]['id'];
8097
                }
8098
            } else {
8099
                if ($arrLP[$i]['item_type'] == 'dir') {
8100
                    $selectParent->addOption(
8101
                        $arrLP[$i]['title'],
8102
                        $arrLP[$i]['id'],
8103
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8104
                    );
8105
8106
                    if ($parent == $arrLP[$i]['id']) {
8107
                        $selectParent->setSelected($arrLP[$i]['id']);
8108
                    }
8109
                }
8110
            }
8111
        }
8112
8113
        if (is_array($arrLP)) {
8114
            reset($arrLP);
8115
        }
8116
8117
        $selectPrevious = $form->addSelect(
8118
            'previous',
8119
            get_lang('Position'),
8120
            [],
8121
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8122
        );
8123
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8124
8125
        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...
8126
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8127
                $arrLP[$i]['id'] != $id
8128
            ) {
8129
                $selectPrevious->addOption(
8130
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8131
                    $arrLP[$i]['id']
8132
                );
8133
8134
                if (isset($extra_info['previous_item_id']) &&
8135
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8136
                ) {
8137
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8138
                } elseif ($action == 'add') {
8139
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8140
                }
8141
            }
8142
        }
8143
8144
        if ($action != 'move') {
8145
            $id_prerequisite = 0;
8146
            if (is_array($arrLP)) {
8147
                foreach ($arrLP as $key => $value) {
8148
                    if ($value['id'] == $id) {
8149
                        $id_prerequisite = $value['prerequisite'];
8150
                        break;
8151
                    }
8152
                }
8153
            }
8154
8155
            $arrHide = [];
8156
            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...
8157
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8158
                    if (isset($extra_info['previous_item_id']) &&
8159
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8160
                    ) {
8161
                        $s_selected_position = $arrLP[$i]['id'];
8162
                    } elseif ($action == 'add') {
8163
                        $s_selected_position = 0;
8164
                    }
8165
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8166
                }
8167
            }
8168
        }
8169
8170
        if ($action == 'add') {
8171
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8172
        } else {
8173
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8174
        }
8175
8176
        if ($action == 'move') {
8177
            $form->addHidden('title', $item_title);
8178
            $form->addHidden('description', $item_description);
8179
        }
8180
8181
        if (is_numeric($extra_info)) {
8182
            $form->addHidden('path', $extra_info);
8183
        } elseif (is_array($extra_info)) {
8184
            $form->addHidden('path', $extra_info['path']);
8185
        }
8186
        $form->addHidden('type', TOOL_FORUM);
8187
        $form->addHidden('post_time', time());
8188
        $form->setDefaults($defaults);
8189
8190
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8191
    }
8192
8193
    /**
8194
     * Return HTML form to add/edit forum threads.
8195
     *
8196
     * @param string $action
8197
     * @param int    $id         Item ID if already exists in learning path
8198
     * @param string $extra_info
8199
     *
8200
     * @throws Exception
8201
     *
8202
     * @return string HTML form
8203
     */
8204
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8205
    {
8206
        $course_id = api_get_course_int_id();
8207
        if (empty($course_id)) {
8208
            return null;
8209
        }
8210
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8211
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8212
8213
        if ($id != 0 && is_array($extra_info)) {
8214
            $item_title = stripslashes($extra_info['title']);
8215
        } elseif (is_numeric($extra_info)) {
8216
            $sql = "SELECT thread_title as title FROM $tbl_forum
8217
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8218
8219
            $result = Database::query($sql);
8220
            $row = Database::fetch_array($result);
8221
8222
            $item_title = $row['title'];
8223
            $item_description = '';
8224
        } else {
8225
            $item_title = '';
8226
            $item_description = '';
8227
        }
8228
8229
        if ($id != 0 && is_array($extra_info)) {
8230
            $parent = $extra_info['parent_item_id'];
8231
        } else {
8232
            $parent = 0;
8233
        }
8234
8235
        $sql = "SELECT * FROM $tbl_lp_item
8236
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8237
        $result = Database::query($sql);
8238
8239
        $arrLP = [];
8240
        while ($row = Database::fetch_array($result)) {
8241
            $arrLP[] = [
8242
                'id' => $row['iid'],
8243
                'item_type' => $row['item_type'],
8244
                'title' => $row['title'],
8245
                'path' => $row['path'],
8246
                'description' => $row['description'],
8247
                'parent_item_id' => $row['parent_item_id'],
8248
                'previous_item_id' => $row['previous_item_id'],
8249
                'next_item_id' => $row['next_item_id'],
8250
                'display_order' => $row['display_order'],
8251
                'max_score' => $row['max_score'],
8252
                'min_score' => $row['min_score'],
8253
                'mastery_score' => $row['mastery_score'],
8254
                'prerequisite' => $row['prerequisite'],
8255
            ];
8256
        }
8257
8258
        $this->tree_array($arrLP);
8259
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8260
        unset($this->arrMenu);
8261
8262
        $form = new FormValidator(
8263
            'thread_form',
8264
            'POST',
8265
            $this->getCurrentBuildingModeURL()
8266
        );
8267
        $defaults = [];
8268
8269
        if ($action == 'add') {
8270
            $legend = get_lang('CreateTheForum');
8271
        } elseif ($action == 'move') {
8272
            $legend = get_lang('MoveTheCurrentForum');
8273
        } else {
8274
            $legend = get_lang('EditCurrentForum');
8275
        }
8276
8277
        $form->addHeader($legend);
8278
        $selectParent = $form->addSelect(
8279
            'parent',
8280
            get_lang('Parent'),
8281
            [],
8282
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8283
        );
8284
        $selectParent->addOption($this->name, 0);
8285
8286
        $arrHide = [
8287
            $id,
8288
        ];
8289
8290
        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...
8291
            if ($action != 'add') {
8292
                if (
8293
                    ($arrLP[$i]['item_type'] == 'dir') &&
8294
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8295
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8296
                ) {
8297
                    $selectParent->addOption(
8298
                        $arrLP[$i]['title'],
8299
                        $arrLP[$i]['id'],
8300
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8301
                    );
8302
8303
                    if ($parent == $arrLP[$i]['id']) {
8304
                        $selectParent->setSelected($arrLP[$i]['id']);
8305
                    }
8306
                } else {
8307
                    $arrHide[] = $arrLP[$i]['id'];
8308
                }
8309
            } else {
8310
                if ($arrLP[$i]['item_type'] == 'dir') {
8311
                    $selectParent->addOption(
8312
                        $arrLP[$i]['title'],
8313
                        $arrLP[$i]['id'],
8314
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8315
                    );
8316
8317
                    if ($parent == $arrLP[$i]['id']) {
8318
                        $selectParent->setSelected($arrLP[$i]['id']);
8319
                    }
8320
                }
8321
            }
8322
        }
8323
8324
        if ($arrLP != null) {
8325
            reset($arrLP);
8326
        }
8327
8328
        $selectPrevious = $form->addSelect(
8329
            'previous',
8330
            get_lang('Position'),
8331
            [],
8332
            ['id' => 'previous']
8333
        );
8334
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8335
8336
        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...
8337
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8338
                $selectPrevious->addOption(
8339
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8340
                    $arrLP[$i]['id']
8341
                );
8342
8343
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8344
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8345
                } elseif ($action == 'add') {
8346
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8347
                }
8348
            }
8349
        }
8350
8351
        if ($action != 'move') {
8352
            $form->addText(
8353
                'title',
8354
                get_lang('Title'),
8355
                true,
8356
                ['id' => 'idTitle']
8357
            );
8358
            $defaults['title'] = $item_title;
8359
8360
            $id_prerequisite = 0;
8361
            if ($arrLP != null) {
8362
                foreach ($arrLP as $key => $value) {
8363
                    if ($value['id'] == $id) {
8364
                        $id_prerequisite = $value['prerequisite'];
8365
                        break;
8366
                    }
8367
                }
8368
            }
8369
8370
            $arrHide = [];
8371
            $s_selected_position = 0;
8372
            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...
8373
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8374
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8375
                        $s_selected_position = $arrLP[$i]['id'];
8376
                    } elseif ($action == 'add') {
8377
                        $s_selected_position = 0;
8378
                    }
8379
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8380
                }
8381
            }
8382
8383
            $selectPrerequisites = $form->addSelect(
8384
                'prerequisites',
8385
                get_lang('LearnpathPrerequisites'),
8386
                [],
8387
                ['id' => 'prerequisites']
8388
            );
8389
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8390
8391
            foreach ($arrHide as $key => $value) {
8392
                $selectPrerequisites->addOption($value['value'], $key);
8393
8394
                if ($key == $s_selected_position && $action == 'add') {
8395
                    $selectPrerequisites->setSelected($key);
8396
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8397
                    $selectPrerequisites->setSelected($key);
8398
                }
8399
            }
8400
        }
8401
8402
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8403
8404
        if ($action == 'move') {
8405
            $form->addHidden('title', $item_title);
8406
            $form->addHidden('description', $item_description);
8407
        }
8408
8409
        if (is_numeric($extra_info)) {
8410
            $form->addHidden('path', $extra_info);
8411
        } elseif (is_array($extra_info)) {
8412
            $form->addHidden('path', $extra_info['path']);
8413
        }
8414
8415
        $form->addHidden('type', TOOL_THREAD);
8416
        $form->addHidden('post_time', time());
8417
        $form->setDefaults($defaults);
8418
8419
        return $form->returnForm();
8420
    }
8421
8422
    /**
8423
     * Return the HTML form to display an item (generally a dir item).
8424
     *
8425
     * @param string $item_type
8426
     * @param string $title
8427
     * @param string $action
8428
     * @param int    $id
8429
     * @param string $extra_info
8430
     *
8431
     * @throws Exception
8432
     * @throws HTML_QuickForm_Error
8433
     *
8434
     * @return string HTML form
8435
     */
8436
    public function display_item_form(
8437
        $item_type,
8438
        $title = '',
8439
        $action = 'add_item',
8440
        $id = 0,
8441
        $extra_info = 'new'
8442
    ) {
8443
        $_course = api_get_course_info();
8444
8445
        global $charset;
8446
8447
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8448
        $item_title = '';
8449
        $item_description = '';
8450
        $item_path_fck = '';
8451
8452
        if ($id != 0 && is_array($extra_info)) {
8453
            $item_title = $extra_info['title'];
8454
            $item_description = $extra_info['description'];
8455
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8456
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8457
        }
8458
        $parent = 0;
8459
        if ($id != 0 && is_array($extra_info)) {
8460
            $parent = $extra_info['parent_item_id'];
8461
        }
8462
8463
        $id = (int) $id;
8464
        $sql = "SELECT * FROM $tbl_lp_item
8465
                WHERE
8466
                    lp_id = ".$this->lp_id." AND
8467
                    iid != $id";
8468
8469
        if ($item_type == 'dir') {
8470
            $sql .= " AND parent_item_id = 0";
8471
        }
8472
8473
        $result = Database::query($sql);
8474
        $arrLP = [];
8475
        while ($row = Database::fetch_array($result)) {
8476
            $arrLP[] = [
8477
                'id' => $row['iid'],
8478
                'item_type' => $row['item_type'],
8479
                'title' => $row['title'],
8480
                'path' => $row['path'],
8481
                'description' => $row['description'],
8482
                'parent_item_id' => $row['parent_item_id'],
8483
                'previous_item_id' => $row['previous_item_id'],
8484
                'next_item_id' => $row['next_item_id'],
8485
                'max_score' => $row['max_score'],
8486
                'min_score' => $row['min_score'],
8487
                'mastery_score' => $row['mastery_score'],
8488
                'prerequisite' => $row['prerequisite'],
8489
                'display_order' => $row['display_order'],
8490
            ];
8491
        }
8492
8493
        $this->tree_array($arrLP);
8494
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8495
        unset($this->arrMenu);
8496
8497
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8498
8499
        $form = new FormValidator('form', 'POST', $url);
8500
        $defaults['title'] = api_html_entity_decode(
8501
            $item_title,
8502
            ENT_QUOTES,
8503
            $charset
8504
        );
8505
        $defaults['description'] = $item_description;
8506
8507
        $form->addHeader($title);
8508
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8509
        $arrHide[0]['padding'] = 20;
8510
        $charset = api_get_system_encoding();
8511
        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...
8512
            if ($action != 'add') {
8513
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8514
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8515
                ) {
8516
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8517
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8518
                    if ($parent == $arrLP[$i]['id']) {
8519
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8520
                    }
8521
                }
8522
            } else {
8523
                if ($arrLP[$i]['item_type'] == 'dir') {
8524
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8525
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8526
                    if ($parent == $arrLP[$i]['id']) {
8527
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8528
                    }
8529
                }
8530
            }
8531
        }
8532
8533
        if ($action != 'move') {
8534
            $form->addElement('text', 'title', get_lang('Title'));
8535
            $form->applyFilter('title', 'html_filter');
8536
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8537
        } else {
8538
            $form->addElement('hidden', 'title');
8539
        }
8540
8541
        $parentSelect = $form->addElement(
8542
            'select',
8543
            'parent',
8544
            get_lang('Parent'),
8545
            '',
8546
            [
8547
                'id' => 'idParent',
8548
                'onchange' => "javascript: load_cbo(this.value);",
8549
            ]
8550
        );
8551
8552
        foreach ($arrHide as $key => $value) {
8553
            $parentSelect->addOption(
8554
                $value['value'],
8555
                $key,
8556
                'style="padding-left:'.$value['padding'].'px;"'
8557
            );
8558
            $lastPosition = $key;
8559
        }
8560
8561
        if (!empty($s_selected_parent)) {
8562
            $parentSelect->setSelected($s_selected_parent);
8563
        }
8564
8565
        if (is_array($arrLP)) {
8566
            reset($arrLP);
8567
        }
8568
        $arrHide = [];
8569
        // POSITION
8570
        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...
8571
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8572
                //this is the same!
8573
                if (isset($extra_info['previous_item_id']) &&
8574
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8575
                ) {
8576
                    $s_selected_position = $arrLP[$i]['id'];
8577
                } elseif ($action == 'add') {
8578
                    $s_selected_position = $arrLP[$i]['id'];
8579
                }
8580
8581
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8582
            }
8583
        }
8584
8585
        $position = $form->addElement(
8586
            'select',
8587
            'previous',
8588
            get_lang('Position'),
8589
            '',
8590
            ['id' => 'previous']
8591
        );
8592
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8593
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8594
8595
        $lastPosition = null;
8596
        foreach ($arrHide as $key => $value) {
8597
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8598
            $lastPosition = $key;
8599
        }
8600
8601
        if (!empty($s_selected_position)) {
8602
            $position->setSelected($s_selected_position);
8603
        }
8604
8605
        // When new chapter add at the end
8606
        if ($action == 'add_item') {
8607
            $position->setSelected($lastPosition);
8608
        }
8609
8610
        if (is_array($arrLP)) {
8611
            reset($arrLP);
8612
        }
8613
8614
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8615
8616
        //fix in order to use the tab
8617
        if ($item_type == 'dir') {
8618
            $form->addElement('hidden', 'type', 'dir');
8619
        }
8620
8621
        $extension = null;
8622
        if (!empty($item_path)) {
8623
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8624
        }
8625
8626
        //assets can't be modified
8627
        //$item_type == 'asset' ||
8628
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8629
            if ($item_type == 'sco') {
8630
                $form->addElement(
8631
                    'html',
8632
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8633
                );
8634
            }
8635
            $renderer = $form->defaultRenderer();
8636
            $renderer->setElementTemplate(
8637
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8638
                'content_lp'
8639
            );
8640
8641
            $relative_prefix = '';
8642
8643
            $editor_config = [
8644
                'ToolbarSet' => 'LearningPathDocuments',
8645
                'Width' => '100%',
8646
                'Height' => '500',
8647
                'FullPage' => true,
8648
                'CreateDocumentDir' => $relative_prefix,
8649
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8650
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8651
            ];
8652
8653
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8654
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8655
            $defaults['content_lp'] = file_get_contents($content_path);
8656
        }
8657
8658
        if (!empty($id)) {
8659
            $form->addHidden('id', $id);
8660
        }
8661
8662
        $form->addElement('hidden', 'type', $item_type);
8663
        $form->addElement('hidden', 'post_time', time());
8664
        $form->setDefaults($defaults);
8665
8666
        return $form->returnForm();
8667
    }
8668
8669
    /**
8670
     * @return string
8671
     */
8672
    public function getCurrentBuildingModeURL()
8673
    {
8674
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8675
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8676
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8677
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8678
8679
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8680
8681
        return $currentUrl;
8682
    }
8683
8684
    /**
8685
     * Returns the form to update or create a document.
8686
     *
8687
     * @param string $action     (add/edit)
8688
     * @param int    $id         ID of the lp_item (if already exists)
8689
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8690
     *
8691
     * @throws Exception
8692
     * @throws HTML_QuickForm_Error
8693
     *
8694
     * @return string HTML form
8695
     */
8696
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8697
    {
8698
        $course_id = api_get_course_int_id();
8699
        $_course = api_get_course_info();
8700
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8701
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8702
8703
        $no_display_edit_textarea = false;
8704
        $item_description = '';
8705
        //If action==edit document
8706
        //We don't display the document form if it's not an editable document (html or txt file)
8707
        if ($action === 'edit') {
8708
            if (is_array($extra_info)) {
8709
                $path_parts = pathinfo($extra_info['dir']);
8710
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8711
                    $no_display_edit_textarea = true;
8712
                }
8713
            }
8714
        }
8715
        $no_display_add = false;
8716
8717
        // If action==add an existing document
8718
        // We don't display the document form if it's not an editable document (html or txt file).
8719
        if ($action === 'add') {
8720
            if (is_numeric($extra_info)) {
8721
                $extra_info = (int) $extra_info;
8722
                $sql_doc = "SELECT path FROM $tbl_doc 
8723
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8724
                $result = Database::query($sql_doc);
8725
                $path_file = Database::result($result, 0, 0);
8726
                $path_parts = pathinfo($path_file);
8727
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8728
                    $no_display_add = true;
8729
                }
8730
            }
8731
        }
8732
        if ($id != 0 && is_array($extra_info)) {
8733
            $item_title = stripslashes($extra_info['title']);
8734
            $item_description = stripslashes($extra_info['description']);
8735
            if (empty($item_title)) {
8736
                $path_parts = pathinfo($extra_info['path']);
8737
                $item_title = stripslashes($path_parts['filename']);
8738
            }
8739
        } elseif (is_numeric($extra_info)) {
8740
            $sql = "SELECT path, title FROM $tbl_doc
8741
                    WHERE
8742
                        c_id = ".$course_id." AND
8743
                        iid = ".intval($extra_info);
8744
            $result = Database::query($sql);
8745
            $row = Database::fetch_array($result);
8746
            $item_title = $row['title'];
8747
            $item_title = str_replace('_', ' ', $item_title);
8748
            if (empty($item_title)) {
8749
                $path_parts = pathinfo($row['path']);
8750
                $item_title = stripslashes($path_parts['filename']);
8751
            }
8752
        } else {
8753
            $item_title = '';
8754
            $item_description = '';
8755
        }
8756
        $return = '<legend>';
8757
        $parent = 0;
8758
        if ($id != 0 && is_array($extra_info)) {
8759
            $parent = $extra_info['parent_item_id'];
8760
        }
8761
8762
        $sql = "SELECT * FROM $tbl_lp_item
8763
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8764
        $result = Database::query($sql);
8765
        $arrLP = [];
8766
8767
        while ($row = Database::fetch_array($result)) {
8768
            $arrLP[] = [
8769
                'id' => $row['iid'],
8770
                'item_type' => $row['item_type'],
8771
                'title' => $row['title'],
8772
                'path' => $row['path'],
8773
                'description' => $row['description'],
8774
                'parent_item_id' => $row['parent_item_id'],
8775
                'previous_item_id' => $row['previous_item_id'],
8776
                'next_item_id' => $row['next_item_id'],
8777
                'display_order' => $row['display_order'],
8778
                'max_score' => $row['max_score'],
8779
                'min_score' => $row['min_score'],
8780
                'mastery_score' => $row['mastery_score'],
8781
                'prerequisite' => $row['prerequisite'],
8782
            ];
8783
        }
8784
8785
        $this->tree_array($arrLP);
8786
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8787
        unset($this->arrMenu);
8788
8789
        if ($action == 'add') {
8790
            $return .= get_lang('CreateTheDocument');
8791
        } elseif ($action == 'move') {
8792
            $return .= get_lang('MoveTheCurrentDocument');
8793
        } else {
8794
            $return .= get_lang('EditTheCurrentDocument');
8795
        }
8796
        $return .= '</legend>';
8797
8798
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8799
            $return .= Display::return_message(
8800
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8801
                false
8802
            );
8803
        }
8804
        $form = new FormValidator(
8805
            'form',
8806
            'POST',
8807
            $this->getCurrentBuildingModeURL(),
8808
            '',
8809
            ['enctype' => 'multipart/form-data']
8810
        );
8811
        $defaults['title'] = Security::remove_XSS($item_title);
8812
        if (empty($item_title)) {
8813
            $defaults['title'] = Security::remove_XSS($item_title);
8814
        }
8815
        $defaults['description'] = $item_description;
8816
        $form->addElement('html', $return);
8817
8818
        if ($action != 'move') {
8819
            $data = $this->generate_lp_folder($_course);
8820
            if ($action != 'edit') {
8821
                $folders = DocumentManager::get_all_document_folders(
8822
                    $_course,
8823
                    0,
8824
                    true
8825
                );
8826
                DocumentManager::build_directory_selector(
8827
                    $folders,
8828
                    '',
8829
                    [],
8830
                    true,
8831
                    $form,
8832
                    'directory_parent_id'
8833
                );
8834
            }
8835
8836
            if (isset($data['id'])) {
8837
                $defaults['directory_parent_id'] = $data['id'];
8838
            }
8839
8840
            $form->addElement(
8841
                'text',
8842
                'title',
8843
                get_lang('Title'),
8844
                ['id' => 'idTitle', 'class' => 'col-md-4']
8845
            );
8846
            $form->applyFilter('title', 'html_filter');
8847
        }
8848
8849
        $arrHide[0]['value'] = $this->name;
8850
        $arrHide[0]['padding'] = 20;
8851
8852
        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...
8853
            if ($action != 'add') {
8854
                if ($arrLP[$i]['item_type'] == 'dir' &&
8855
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8856
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8857
                ) {
8858
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8859
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8860
                }
8861
            } else {
8862
                if ($arrLP[$i]['item_type'] == 'dir') {
8863
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8864
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8865
                }
8866
            }
8867
        }
8868
8869
        $parentSelect = $form->addSelect(
8870
            'parent',
8871
            get_lang('Parent'),
8872
            [],
8873
            [
8874
                'id' => 'idParent',
8875
                'onchange' => 'javascript: load_cbo(this.value);',
8876
            ]
8877
        );
8878
8879
        $my_count = 0;
8880
        foreach ($arrHide as $key => $value) {
8881
            if ($my_count != 0) {
8882
                // The LP name is also the first section and is not in the same charset like the other sections.
8883
                $value['value'] = Security::remove_XSS($value['value']);
8884
                $parentSelect->addOption(
8885
                    $value['value'],
8886
                    $key,
8887
                    'style="padding-left:'.$value['padding'].'px;"'
8888
                );
8889
            } else {
8890
                $value['value'] = Security::remove_XSS($value['value']);
8891
                $parentSelect->addOption(
8892
                    $value['value'],
8893
                    $key,
8894
                    'style="padding-left:'.$value['padding'].'px;"'
8895
                );
8896
            }
8897
            $my_count++;
8898
        }
8899
8900
        if (!empty($id)) {
8901
            $parentSelect->setSelected($parent);
8902
        } else {
8903
            $parent_item_id = Session::read('parent_item_id', 0);
8904
            $parentSelect->setSelected($parent_item_id);
8905
        }
8906
8907
        if (is_array($arrLP)) {
8908
            reset($arrLP);
8909
        }
8910
8911
        $arrHide = [];
8912
        // POSITION
8913
        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...
8914
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8915
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8916
            ) {
8917
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8918
            }
8919
        }
8920
8921
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8922
8923
        $position = $form->addSelect(
8924
            'previous',
8925
            get_lang('Position'),
8926
            [],
8927
            ['id' => 'previous']
8928
        );
8929
8930
        $position->addOption(get_lang('FirstPosition'), 0);
8931
        foreach ($arrHide as $key => $value) {
8932
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8933
            $position->addOption(
8934
                $value['value'],
8935
                $key,
8936
                'style="padding-left:'.$padding.'px;"'
8937
            );
8938
        }
8939
8940
        $position->setSelected($selectedPosition);
8941
8942
        if (is_array($arrLP)) {
8943
            reset($arrLP);
8944
        }
8945
8946
        if ($action != 'move') {
8947
            $arrHide = [];
8948
            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...
8949
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8950
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8951
                ) {
8952
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8953
                }
8954
            }
8955
8956
            if (!$no_display_add) {
8957
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8958
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8959
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8960
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8961
                ) {
8962
                    if (isset($_POST['content'])) {
8963
                        $content = stripslashes($_POST['content']);
8964
                    } elseif (is_array($extra_info)) {
8965
                        //If it's an html document or a text file
8966
                        if (!$no_display_edit_textarea) {
8967
                            $content = $this->display_document(
8968
                                $extra_info['path'],
8969
                                false,
8970
                                false
8971
                            );
8972
                        }
8973
                    } elseif (is_numeric($extra_info)) {
8974
                        $content = $this->display_document(
8975
                            $extra_info,
8976
                            false,
8977
                            false
8978
                        );
8979
                    } else {
8980
                        $content = '';
8981
                    }
8982
8983
                    if (!$no_display_edit_textarea) {
8984
                        // We need to calculate here some specific settings for the online editor.
8985
                        // The calculated settings work for documents in the Documents tool
8986
                        // (on the root or in subfolders).
8987
                        // For documents in native scorm packages it is unclear whether the
8988
                        // online editor should be activated or not.
8989
8990
                        // A new document, it is in the root of the repository.
8991
                        $relative_path = '';
8992
                        $relative_prefix = '';
8993
                        if (is_array($extra_info) && $extra_info != 'new') {
8994
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8995
                            $relative_path = explode('/', $extra_info['dir']);
8996
                            $cnt = count($relative_path) - 2;
8997
                            if ($cnt < 0) {
8998
                                $cnt = 0;
8999
                            }
9000
                            $relative_prefix = str_repeat('../', $cnt);
9001
                            $relative_path = array_slice($relative_path, 1, $cnt);
9002
                            $relative_path = implode('/', $relative_path);
9003
                            if (strlen($relative_path) > 0) {
9004
                                $relative_path = $relative_path.'/';
9005
                            }
9006
                        } else {
9007
                            $result = $this->generate_lp_folder($_course);
9008
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9009
                            $relative_prefix = '../../';
9010
                        }
9011
9012
                        $editor_config = [
9013
                            'ToolbarSet' => 'LearningPathDocuments',
9014
                            'Width' => '100%',
9015
                            'Height' => '500',
9016
                            'FullPage' => true,
9017
                            'CreateDocumentDir' => $relative_prefix,
9018
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9019
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9020
                        ];
9021
9022
                        if ($_GET['action'] == 'add_item') {
9023
                            $class = 'add';
9024
                            $text = get_lang('LPCreateDocument');
9025
                        } else {
9026
                            if ($_GET['action'] == 'edit_item') {
9027
                                $class = 'save';
9028
                                $text = get_lang('SaveDocument');
9029
                            }
9030
                        }
9031
9032
                        $form->addButtonSave($text, 'submit_button');
9033
                        $renderer = $form->defaultRenderer();
9034
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9035
                        $form->addElement('html', '<div class="editor-lp">');
9036
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9037
                        $form->addElement('html', '</div>');
9038
                        $defaults['content_lp'] = $content;
9039
                    }
9040
                } elseif (is_numeric($extra_info)) {
9041
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9042
9043
                    $return = $this->display_document($extra_info, true, true, true);
9044
                    $form->addElement('html', $return);
9045
                }
9046
            }
9047
        }
9048
        if (isset($extra_info['item_type']) &&
9049
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9050
        ) {
9051
            $parentSelect->freeze();
9052
            $position->freeze();
9053
        }
9054
9055
        if ($action == 'move') {
9056
            $form->addElement('hidden', 'title', $item_title);
9057
            $form->addElement('hidden', 'description', $item_description);
9058
        }
9059
        if (is_numeric($extra_info)) {
9060
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9061
            $form->addElement('hidden', 'path', $extra_info);
9062
        } elseif (is_array($extra_info)) {
9063
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9064
            $form->addElement('hidden', 'path', $extra_info['path']);
9065
        }
9066
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9067
        $form->addElement('hidden', 'post_time', time());
9068
        $form->setDefaults($defaults);
9069
9070
        return $form->returnForm();
9071
    }
9072
9073
    /**
9074
     * Returns the form to update or create a read-out text.
9075
     *
9076
     * @param string $action     "add" or "edit"
9077
     * @param int    $id         ID of the lp_item (if already exists)
9078
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9079
     *
9080
     * @throws Exception
9081
     * @throws HTML_QuickForm_Error
9082
     *
9083
     * @return string HTML form
9084
     */
9085
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9086
    {
9087
        $course_id = api_get_course_int_id();
9088
        $_course = api_get_course_info();
9089
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9090
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9091
9092
        $no_display_edit_textarea = false;
9093
        $item_description = '';
9094
        //If action==edit document
9095
        //We don't display the document form if it's not an editable document (html or txt file)
9096
        if ($action == 'edit') {
9097
            if (is_array($extra_info)) {
9098
                $path_parts = pathinfo($extra_info['dir']);
9099
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9100
                    $no_display_edit_textarea = true;
9101
                }
9102
            }
9103
        }
9104
        $no_display_add = false;
9105
9106
        if ($id != 0 && is_array($extra_info)) {
9107
            $item_title = stripslashes($extra_info['title']);
9108
            $item_description = stripslashes($extra_info['description']);
9109
            $item_terms = stripslashes($extra_info['terms']);
9110
            if (empty($item_title)) {
9111
                $path_parts = pathinfo($extra_info['path']);
9112
                $item_title = stripslashes($path_parts['filename']);
9113
            }
9114
        } elseif (is_numeric($extra_info)) {
9115
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9116
            $result = Database::query($sql);
9117
            $row = Database::fetch_array($result);
9118
            $item_title = $row['title'];
9119
            $item_title = str_replace('_', ' ', $item_title);
9120
            if (empty($item_title)) {
9121
                $path_parts = pathinfo($row['path']);
9122
                $item_title = stripslashes($path_parts['filename']);
9123
            }
9124
        } else {
9125
            $item_title = '';
9126
            $item_description = '';
9127
        }
9128
9129
        if ($id != 0 && is_array($extra_info)) {
9130
            $parent = $extra_info['parent_item_id'];
9131
        } else {
9132
            $parent = 0;
9133
        }
9134
9135
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9136
        $result = Database::query($sql);
9137
        $arrLP = [];
9138
9139
        while ($row = Database::fetch_array($result)) {
9140
            $arrLP[] = [
9141
                'id' => $row['iid'],
9142
                'item_type' => $row['item_type'],
9143
                'title' => $row['title'],
9144
                'path' => $row['path'],
9145
                'description' => $row['description'],
9146
                'parent_item_id' => $row['parent_item_id'],
9147
                'previous_item_id' => $row['previous_item_id'],
9148
                'next_item_id' => $row['next_item_id'],
9149
                'display_order' => $row['display_order'],
9150
                'max_score' => $row['max_score'],
9151
                'min_score' => $row['min_score'],
9152
                'mastery_score' => $row['mastery_score'],
9153
                'prerequisite' => $row['prerequisite'],
9154
            ];
9155
        }
9156
9157
        $this->tree_array($arrLP);
9158
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9159
        unset($this->arrMenu);
9160
9161
        if ($action == 'add') {
9162
            $formHeader = get_lang('CreateTheDocument');
9163
        } else {
9164
            $formHeader = get_lang('EditTheCurrentDocument');
9165
        }
9166
9167
        if ('edit' === $action) {
9168
            $urlAudioIcon = Display::url(
9169
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9170
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9171
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9172
            );
9173
        } else {
9174
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9175
        }
9176
9177
        $form = new FormValidator(
9178
            'frm_add_reading',
9179
            'POST',
9180
            $this->getCurrentBuildingModeURL(),
9181
            '',
9182
            ['enctype' => 'multipart/form-data']
9183
        );
9184
        $form->addHeader($formHeader);
9185
        $form->addHtml(
9186
            Display::return_message(
9187
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9188
                'normal',
9189
                false
9190
            )
9191
        );
9192
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9193
        $defaults['description'] = $item_description;
9194
9195
        $data = $this->generate_lp_folder($_course);
9196
9197
        if ($action != 'edit') {
9198
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9199
            DocumentManager::build_directory_selector(
9200
                $folders,
9201
                '',
9202
                [],
9203
                true,
9204
                $form,
9205
                'directory_parent_id'
9206
            );
9207
        }
9208
9209
        if (isset($data['id'])) {
9210
            $defaults['directory_parent_id'] = $data['id'];
9211
        }
9212
9213
        $form->addElement(
9214
            'text',
9215
            'title',
9216
            get_lang('Title')
9217
        );
9218
        $form->applyFilter('title', 'trim');
9219
        $form->applyFilter('title', 'html_filter');
9220
9221
        $arrHide[0]['value'] = $this->name;
9222
        $arrHide[0]['padding'] = 20;
9223
9224
        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...
9225
            if ($action != 'add') {
9226
                if ($arrLP[$i]['item_type'] == 'dir' &&
9227
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9228
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9229
                ) {
9230
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9231
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9232
                }
9233
            } else {
9234
                if ($arrLP[$i]['item_type'] == 'dir') {
9235
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9236
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9237
                }
9238
            }
9239
        }
9240
9241
        $parent_select = $form->addSelect(
9242
            'parent',
9243
            get_lang('Parent'),
9244
            [],
9245
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9246
        );
9247
9248
        $my_count = 0;
9249
        foreach ($arrHide as $key => $value) {
9250
            if ($my_count != 0) {
9251
                // The LP name is also the first section and is not in the same charset like the other sections.
9252
                $value['value'] = Security::remove_XSS($value['value']);
9253
                $parent_select->addOption(
9254
                    $value['value'],
9255
                    $key,
9256
                    'style="padding-left:'.$value['padding'].'px;"'
9257
                );
9258
            } else {
9259
                $value['value'] = Security::remove_XSS($value['value']);
9260
                $parent_select->addOption(
9261
                    $value['value'],
9262
                    $key,
9263
                    'style="padding-left:'.$value['padding'].'px;"'
9264
                );
9265
            }
9266
            $my_count++;
9267
        }
9268
9269
        if (!empty($id)) {
9270
            $parent_select->setSelected($parent);
9271
        } else {
9272
            $parent_item_id = Session::read('parent_item_id', 0);
9273
            $parent_select->setSelected($parent_item_id);
9274
        }
9275
9276
        if (is_array($arrLP)) {
9277
            reset($arrLP);
9278
        }
9279
9280
        $arrHide = [];
9281
        $s_selected_position = null;
9282
9283
        // POSITION
9284
        $lastPosition = null;
9285
9286
        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...
9287
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9288
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9289
            ) {
9290
                if ((isset($extra_info['previous_item_id']) &&
9291
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9292
                ) {
9293
                    $s_selected_position = $arrLP[$i]['id'];
9294
                }
9295
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9296
            }
9297
            $lastPosition = $arrLP[$i]['id'];
9298
        }
9299
9300
        if (empty($s_selected_position)) {
9301
            $s_selected_position = $lastPosition;
9302
        }
9303
9304
        $position = $form->addSelect(
9305
            'previous',
9306
            get_lang('Position'),
9307
            []
9308
        );
9309
        $position->addOption(get_lang('FirstPosition'), 0);
9310
9311
        foreach ($arrHide as $key => $value) {
9312
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9313
            $position->addOption(
9314
                $value['value'],
9315
                $key,
9316
                'style="padding-left:'.$padding.'px;"'
9317
            );
9318
        }
9319
        $position->setSelected($s_selected_position);
9320
9321
        if (is_array($arrLP)) {
9322
            reset($arrLP);
9323
        }
9324
9325
        $arrHide = [];
9326
9327
        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...
9328
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9329
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9330
            ) {
9331
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9332
            }
9333
        }
9334
9335
        if (!$no_display_add) {
9336
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9337
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9338
9339
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9340
                if (!$no_display_edit_textarea) {
9341
                    $content = '';
9342
9343
                    if (isset($_POST['content'])) {
9344
                        $content = stripslashes($_POST['content']);
9345
                    } elseif (is_array($extra_info)) {
9346
                        $content = $this->display_document($extra_info['path'], false, false);
9347
                    } elseif (is_numeric($extra_info)) {
9348
                        $content = $this->display_document($extra_info, false, false);
9349
                    }
9350
9351
                    // A new document, it is in the root of the repository.
9352
                    if (is_array($extra_info) && $extra_info != 'new') {
9353
                    } else {
9354
                        $this->generate_lp_folder($_course);
9355
                    }
9356
9357
                    if ($_GET['action'] == 'add_item') {
9358
                        $text = get_lang('LPCreateDocument');
9359
                    } else {
9360
                        $text = get_lang('SaveDocument');
9361
                    }
9362
9363
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9364
                    $form
9365
                        ->defaultRenderer()
9366
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9367
                    $form->addButtonSave($text, 'submit_button');
9368
                    $defaults['content_lp'] = $content;
9369
                }
9370
            } elseif (is_numeric($extra_info)) {
9371
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9372
9373
                $return = $this->display_document($extra_info, true, true, true);
9374
                $form->addElement('html', $return);
9375
            }
9376
        }
9377
9378
        if (is_numeric($extra_info)) {
9379
            $form->addElement('hidden', 'path', $extra_info);
9380
        } elseif (is_array($extra_info)) {
9381
            $form->addElement('hidden', 'path', $extra_info['path']);
9382
        }
9383
9384
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9385
        $form->addElement('hidden', 'post_time', time());
9386
        $form->setDefaults($defaults);
9387
9388
        return $form->returnForm();
9389
    }
9390
9391
    /**
9392
     * @param array  $courseInfo
9393
     * @param string $content
9394
     * @param string $title
9395
     * @param int    $parentId
9396
     *
9397
     * @throws \Doctrine\ORM\ORMException
9398
     * @throws \Doctrine\ORM\OptimisticLockException
9399
     * @throws \Doctrine\ORM\TransactionRequiredException
9400
     *
9401
     * @return int
9402
     */
9403
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9404
    {
9405
        $creatorId = api_get_user_id();
9406
        $sessionId = api_get_session_id();
9407
9408
        // Generates folder
9409
        $result = $this->generate_lp_folder($courseInfo);
9410
        $dir = $result['dir'];
9411
9412
        if (empty($parentId) || $parentId == '/') {
9413
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9414
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9415
9416
            if ($parentId === '/') {
9417
                $dir = '/';
9418
            }
9419
9420
            // Please, do not modify this dirname formatting.
9421
            if (strstr($dir, '..')) {
9422
                $dir = '/';
9423
            }
9424
9425
            if (!empty($dir[0]) && $dir[0] == '.') {
9426
                $dir = substr($dir, 1);
9427
            }
9428
            if (!empty($dir[0]) && $dir[0] != '/') {
9429
                $dir = '/'.$dir;
9430
            }
9431
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9432
                $dir .= '/';
9433
            }
9434
        } else {
9435
            $parentInfo = DocumentManager::get_document_data_by_id(
9436
                $parentId,
9437
                $courseInfo['code']
9438
            );
9439
            if (!empty($parentInfo)) {
9440
                $dir = $parentInfo['path'].'/';
9441
            }
9442
        }
9443
9444
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9445
9446
        if (!is_dir($filepath)) {
9447
            $dir = '/';
9448
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9449
        }
9450
9451
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9452
9453
        if (!empty($title)) {
9454
            $title = api_replace_dangerous_char(stripslashes($title));
9455
        } else {
9456
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9457
        }
9458
9459
        $title = disable_dangerous_file($title);
9460
        $filename = $title;
9461
        $content = !empty($content) ? $content : $_POST['content_lp'];
9462
        $tmpFileName = $filename;
9463
9464
        $i = 0;
9465
        while (file_exists($filepath.$tmpFileName.'.html')) {
9466
            $tmpFileName = $filename.'_'.++$i;
9467
        }
9468
9469
        $filename = $tmpFileName.'.html';
9470
        $content = stripslashes($content);
9471
9472
        if (file_exists($filepath.$filename)) {
9473
            return 0;
9474
        }
9475
9476
        $putContent = file_put_contents($filepath.$filename, $content);
9477
9478
        if ($putContent === false) {
9479
            return 0;
9480
        }
9481
9482
        $fileSize = filesize($filepath.$filename);
9483
        $saveFilePath = $dir.$filename;
9484
9485
        $document = DocumentManager::addDocument(
9486
            $courseInfo,
9487
            $saveFilePath,
9488
            'file',
9489
            $fileSize,
9490
            $tmpFileName,
9491
            '',
9492
            0, //readonly
9493
            true,
9494
            null,
9495
            $sessionId,
9496
            $creatorId
9497
        );
9498
9499
        $documentId = $document->getId();
9500
9501
        if (!$document) {
9502
            return 0;
9503
        }
9504
9505
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9506
        $newTitle = $originalTitle;
9507
9508
        if ($newComment || $newTitle) {
9509
            $em = Database::getManager();
9510
9511
            if ($newComment) {
9512
                $document->setComment($newComment);
9513
            }
9514
9515
            if ($newTitle) {
9516
                $document->setTitle($newTitle);
9517
            }
9518
9519
            $em->persist($document);
9520
            $em->flush();
9521
        }
9522
9523
        return $documentId;
9524
    }
9525
9526
    /**
9527
     * Return HTML form to add/edit a link item.
9528
     *
9529
     * @param string $action     (add/edit)
9530
     * @param int    $id         Item ID if exists
9531
     * @param mixed  $extra_info
9532
     *
9533
     * @throws Exception
9534
     * @throws HTML_QuickForm_Error
9535
     *
9536
     * @return string HTML form
9537
     */
9538
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9539
    {
9540
        $course_id = api_get_course_int_id();
9541
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9542
        $tbl_link = Database::get_course_table(TABLE_LINK);
9543
9544
        $item_title = '';
9545
        $item_description = '';
9546
        $item_url = '';
9547
9548
        if ($id != 0 && is_array($extra_info)) {
9549
            $item_title = stripslashes($extra_info['title']);
9550
            $item_description = stripslashes($extra_info['description']);
9551
            $item_url = stripslashes($extra_info['url']);
9552
        } elseif (is_numeric($extra_info)) {
9553
            $extra_info = (int) $extra_info;
9554
            $sql = "SELECT title, description, url 
9555
                    FROM $tbl_link
9556
                    WHERE c_id = $course_id AND iid = $extra_info";
9557
            $result = Database::query($sql);
9558
            $row = Database::fetch_array($result);
9559
            $item_title = $row['title'];
9560
            $item_description = $row['description'];
9561
            $item_url = $row['url'];
9562
        }
9563
9564
        $form = new FormValidator(
9565
            'edit_link',
9566
            'POST',
9567
            $this->getCurrentBuildingModeURL()
9568
        );
9569
        $defaults = [];
9570
        $parent = 0;
9571
        if ($id != 0 && is_array($extra_info)) {
9572
            $parent = $extra_info['parent_item_id'];
9573
        }
9574
9575
        $sql = "SELECT * FROM $tbl_lp_item
9576
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9577
        $result = Database::query($sql);
9578
        $arrLP = [];
9579
9580
        while ($row = Database::fetch_array($result)) {
9581
            $arrLP[] = [
9582
                'id' => $row['id'],
9583
                'item_type' => $row['item_type'],
9584
                'title' => $row['title'],
9585
                'path' => $row['path'],
9586
                'description' => $row['description'],
9587
                'parent_item_id' => $row['parent_item_id'],
9588
                'previous_item_id' => $row['previous_item_id'],
9589
                'next_item_id' => $row['next_item_id'],
9590
                'display_order' => $row['display_order'],
9591
                'max_score' => $row['max_score'],
9592
                'min_score' => $row['min_score'],
9593
                'mastery_score' => $row['mastery_score'],
9594
                'prerequisite' => $row['prerequisite'],
9595
            ];
9596
        }
9597
9598
        $this->tree_array($arrLP);
9599
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9600
        unset($this->arrMenu);
9601
9602
        if ($action == 'add') {
9603
            $legend = get_lang('CreateTheLink');
9604
        } elseif ($action == 'move') {
9605
            $legend = get_lang('MoveCurrentLink');
9606
        } else {
9607
            $legend = get_lang('EditCurrentLink');
9608
        }
9609
9610
        $form->addHeader($legend);
9611
9612
        if ($action != 'move') {
9613
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9614
            $defaults['title'] = $item_title;
9615
        }
9616
9617
        $selectParent = $form->addSelect(
9618
            'parent',
9619
            get_lang('Parent'),
9620
            [],
9621
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9622
        );
9623
        $selectParent->addOption($this->name, 0);
9624
        $arrHide = [
9625
            $id,
9626
        ];
9627
9628
        $parent_item_id = Session::read('parent_item_id', 0);
9629
9630
        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...
9631
            if ($action != 'add') {
9632
                if (
9633
                    ($arrLP[$i]['item_type'] == 'dir') &&
9634
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9635
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9636
                ) {
9637
                    $selectParent->addOption(
9638
                        $arrLP[$i]['title'],
9639
                        $arrLP[$i]['id'],
9640
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9641
                    );
9642
9643
                    if ($parent == $arrLP[$i]['id']) {
9644
                        $selectParent->setSelected($arrLP[$i]['id']);
9645
                    }
9646
                } else {
9647
                    $arrHide[] = $arrLP[$i]['id'];
9648
                }
9649
            } else {
9650
                if ($arrLP[$i]['item_type'] == 'dir') {
9651
                    $selectParent->addOption(
9652
                        $arrLP[$i]['title'],
9653
                        $arrLP[$i]['id'],
9654
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9655
                    );
9656
9657
                    if ($parent_item_id == $arrLP[$i]['id']) {
9658
                        $selectParent->setSelected($arrLP[$i]['id']);
9659
                    }
9660
                }
9661
            }
9662
        }
9663
9664
        if (is_array($arrLP)) {
9665
            reset($arrLP);
9666
        }
9667
9668
        $selectPrevious = $form->addSelect(
9669
            'previous',
9670
            get_lang('Position'),
9671
            [],
9672
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9673
        );
9674
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9675
9676
        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...
9677
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9678
                $selectPrevious->addOption(
9679
                    $arrLP[$i]['title'],
9680
                    $arrLP[$i]['id']
9681
                );
9682
9683
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9684
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9685
                } elseif ($action == 'add') {
9686
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9687
                }
9688
            }
9689
        }
9690
9691
        if ($action != 'move') {
9692
            $urlAttributes = ['class' => 'learnpath_item_form'];
9693
9694
            if (is_numeric($extra_info)) {
9695
                $urlAttributes['disabled'] = 'disabled';
9696
            }
9697
9698
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9699
            $defaults['url'] = $item_url;
9700
            $arrHide = [];
9701
            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...
9702
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9703
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9704
                }
9705
            }
9706
        }
9707
9708
        if ($action == 'add') {
9709
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9710
        } else {
9711
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9712
        }
9713
9714
        if ($action == 'move') {
9715
            $form->addHidden('title', $item_title);
9716
            $form->addHidden('description', $item_description);
9717
        }
9718
9719
        if (is_numeric($extra_info)) {
9720
            $form->addHidden('path', $extra_info);
9721
        } elseif (is_array($extra_info)) {
9722
            $form->addHidden('path', $extra_info['path']);
9723
        }
9724
        $form->addHidden('type', TOOL_LINK);
9725
        $form->addHidden('post_time', time());
9726
        $form->setDefaults($defaults);
9727
9728
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9729
    }
9730
9731
    /**
9732
     * Return HTML form to add/edit a student publication (work).
9733
     *
9734
     * @param string $action
9735
     * @param int    $id         Item ID if already exists
9736
     * @param string $extra_info
9737
     *
9738
     * @throws Exception
9739
     *
9740
     * @return string HTML form
9741
     */
9742
    public function display_student_publication_form(
9743
        $action = 'add',
9744
        $id = 0,
9745
        $extra_info = ''
9746
    ) {
9747
        $course_id = api_get_course_int_id();
9748
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9749
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9750
9751
        $item_title = get_lang('Student_publication');
9752
        if ($id != 0 && is_array($extra_info)) {
9753
            $item_title = stripslashes($extra_info['title']);
9754
            $item_description = stripslashes($extra_info['description']);
9755
        } elseif (is_numeric($extra_info)) {
9756
            $extra_info = (int) $extra_info;
9757
            $sql = "SELECT title, description
9758
                    FROM $tbl_publication
9759
                    WHERE c_id = $course_id AND id = ".$extra_info;
9760
9761
            $result = Database::query($sql);
9762
            $row = Database::fetch_array($result);
9763
            if ($row) {
9764
                $item_title = $row['title'];
9765
            }
9766
        }
9767
9768
        $parent = 0;
9769
        if ($id != 0 && is_array($extra_info)) {
9770
            $parent = $extra_info['parent_item_id'];
9771
        }
9772
9773
        $sql = "SELECT * FROM $tbl_lp_item 
9774
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9775
        $result = Database::query($sql);
9776
        $arrLP = [];
9777
9778
        while ($row = Database::fetch_array($result)) {
9779
            $arrLP[] = [
9780
                'id' => $row['iid'],
9781
                'item_type' => $row['item_type'],
9782
                'title' => $row['title'],
9783
                'path' => $row['path'],
9784
                'description' => $row['description'],
9785
                'parent_item_id' => $row['parent_item_id'],
9786
                'previous_item_id' => $row['previous_item_id'],
9787
                'next_item_id' => $row['next_item_id'],
9788
                'display_order' => $row['display_order'],
9789
                'max_score' => $row['max_score'],
9790
                'min_score' => $row['min_score'],
9791
                'mastery_score' => $row['mastery_score'],
9792
                'prerequisite' => $row['prerequisite'],
9793
            ];
9794
        }
9795
9796
        $this->tree_array($arrLP);
9797
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9798
        unset($this->arrMenu);
9799
9800
        $form = new FormValidator('frm_student_publication', 'post', '#');
9801
9802
        if ($action == 'add') {
9803
            $form->addHeader(get_lang('Student_publication'));
9804
        } elseif ($action == 'move') {
9805
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9806
        } else {
9807
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9808
        }
9809
9810
        if ($action != 'move') {
9811
            $form->addText(
9812
                'title',
9813
                get_lang('Title'),
9814
                true,
9815
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9816
            );
9817
        }
9818
9819
        $parentSelect = $form->addSelect(
9820
            'parent',
9821
            get_lang('Parent'),
9822
            ['0' => $this->name],
9823
            [
9824
                'onchange' => 'javascript: load_cbo(this.value);',
9825
                'class' => 'learnpath_item_form',
9826
                'id' => 'idParent',
9827
            ]
9828
        );
9829
9830
        $arrHide = [$id];
9831
        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...
9832
            if ($action != 'add') {
9833
                if (
9834
                    ($arrLP[$i]['item_type'] == 'dir') &&
9835
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9836
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9837
                ) {
9838
                    $parentSelect->addOption(
9839
                        $arrLP[$i]['title'],
9840
                        $arrLP[$i]['id'],
9841
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9842
                    );
9843
9844
                    if ($parent == $arrLP[$i]['id']) {
9845
                        $parentSelect->setSelected($arrLP[$i]['id']);
9846
                    }
9847
                } else {
9848
                    $arrHide[] = $arrLP[$i]['id'];
9849
                }
9850
            } else {
9851
                if ($arrLP[$i]['item_type'] == 'dir') {
9852
                    $parentSelect->addOption(
9853
                        $arrLP[$i]['title'],
9854
                        $arrLP[$i]['id'],
9855
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9856
                    );
9857
9858
                    if ($parent == $arrLP[$i]['id']) {
9859
                        $parentSelect->setSelected($arrLP[$i]['id']);
9860
                    }
9861
                }
9862
            }
9863
        }
9864
9865
        if (is_array($arrLP)) {
9866
            reset($arrLP);
9867
        }
9868
9869
        $previousSelect = $form->addSelect(
9870
            'previous',
9871
            get_lang('Position'),
9872
            ['0' => get_lang('FirstPosition')],
9873
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9874
        );
9875
9876
        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...
9877
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9878
                $previousSelect->addOption(
9879
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9880
                    $arrLP[$i]['id']
9881
                );
9882
9883
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9884
                    $previousSelect->setSelected($arrLP[$i]['id']);
9885
                } elseif ($action == 'add') {
9886
                    $previousSelect->setSelected($arrLP[$i]['id']);
9887
                }
9888
            }
9889
        }
9890
9891
        if ($action == 'add') {
9892
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9893
        } else {
9894
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9895
        }
9896
9897
        if ($action == 'move') {
9898
            $form->addHidden('title', $item_title);
9899
            $form->addHidden('description', $item_description);
9900
        }
9901
9902
        if (is_numeric($extra_info)) {
9903
            $form->addHidden('path', $extra_info);
9904
        } elseif (is_array($extra_info)) {
9905
            $form->addHidden('path', $extra_info['path']);
9906
        }
9907
9908
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9909
        $form->addHidden('post_time', time());
9910
        $form->setDefaults(['title' => $item_title]);
9911
9912
        $return = '<div class="sectioncomment">';
9913
        $return .= $form->returnForm();
9914
        $return .= '</div>';
9915
9916
        return $return;
9917
    }
9918
9919
    /**
9920
     * Displays the menu for manipulating a step.
9921
     *
9922
     * @param id     $item_id
9923
     * @param string $item_type
9924
     *
9925
     * @return string
9926
     */
9927
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9928
    {
9929
        $_course = api_get_course_info();
9930
        $course_code = api_get_course_id();
9931
        $return = '<div class="actions">';
9932
        switch ($item_type) {
9933
            case 'dir':
9934
                // Commented the message cause should not show it.
9935
                //$lang = get_lang('TitleManipulateChapter');
9936
                break;
9937
            case TOOL_LP_FINAL_ITEM:
9938
            case TOOL_DOCUMENT:
9939
                // Commented the message cause should not show it.
9940
                //$lang = get_lang('TitleManipulateDocument');
9941
                break;
9942
            case TOOL_LINK:
9943
            case 'link':
9944
                // Commented the message cause should not show it.
9945
                //$lang = get_lang('TitleManipulateLink');
9946
                break;
9947
            case TOOL_QUIZ:
9948
                // Commented the message cause should not show it.
9949
                //$lang = get_lang('TitleManipulateQuiz');
9950
                break;
9951
            case TOOL_STUDENTPUBLICATION:
9952
                // Commented the message cause should not show it.
9953
                //$lang = get_lang('TitleManipulateStudentPublication');
9954
                break;
9955
        }
9956
9957
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9958
        $item_id = (int) $item_id;
9959
        $sql = "SELECT * FROM $tbl_lp_item 
9960
                WHERE iid = ".$item_id;
9961
        $result = Database::query($sql);
9962
        $row = Database::fetch_assoc($result);
9963
9964
        $audio_player = null;
9965
        // We display an audio player if needed.
9966
        if (!empty($row['audio'])) {
9967
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9968
9969
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9970
                .'<audio src="'.$webAudioPath.'" controls>'
9971
                .'</div><br>';
9972
        }
9973
9974
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9975
9976
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9977
            $return .= Display::url(
9978
                Display::return_icon(
9979
                    'edit.png',
9980
                    get_lang('Edit'),
9981
                    [],
9982
                    ICON_SIZE_SMALL
9983
                ),
9984
                $url.'&action=edit_item&path_item='.$row['path']
9985
            );
9986
9987
            $return .= Display::url(
9988
                Display::return_icon(
9989
                    'move.png',
9990
                    get_lang('Move'),
9991
                    [],
9992
                    ICON_SIZE_SMALL
9993
                ),
9994
                $url.'&action=move_item'
9995
            );
9996
        }
9997
9998
        // Commented for now as prerequisites cannot be added to chapters.
9999
        if ($item_type != 'dir') {
10000
            $return .= Display::url(
10001
                Display::return_icon(
10002
                    'accept.png',
10003
                    get_lang('LearnpathPrerequisites'),
10004
                    [],
10005
                    ICON_SIZE_SMALL
10006
                ),
10007
                $url.'&action=edit_item_prereq'
10008
            );
10009
        }
10010
        $return .= Display::url(
10011
            Display::return_icon(
10012
                'delete.png',
10013
                get_lang('Delete'),
10014
                [],
10015
                ICON_SIZE_SMALL
10016
            ),
10017
            $url.'&action=delete_item'
10018
        );
10019
10020
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10021
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10022
            if (empty($documentData)) {
10023
10024
                $table = Database::get_course_table(TABLE_DOCUMENT);
10025
                $sql = "SELECT path FROM $table
10026
                        WHERE 
10027
                              c_id = ".api_get_course_int_id()." AND 
10028
                              iid = ".$row['path']." AND 
10029
                              path NOT LIKE '%_DELETED_%'";
10030
                $result = Database::query($sql);
10031
                $documentData = Database::fetch_array($result);
10032
                if ($documentData) {
10033
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10034
                }
10035
            }
10036
            if (isset($documentData['absolute_path_from_document'])) {
10037
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10038
            }
10039
        }
10040
10041
        $return .= '</div>';
10042
10043
        if (!empty($audio_player)) {
10044
            $return .= $audio_player;
10045
        }
10046
10047
        return $return;
10048
    }
10049
10050
    /**
10051
     * Creates the javascript needed for filling up the checkboxes without page reload.
10052
     *
10053
     * @return string
10054
     */
10055
    public function get_js_dropdown_array()
10056
    {
10057
        $course_id = api_get_course_int_id();
10058
        $return = 'var child_name = new Array();'."\n";
10059
        $return .= 'var child_value = new Array();'."\n\n";
10060
        $return .= 'child_name[0] = new Array();'."\n";
10061
        $return .= 'child_value[0] = new Array();'."\n\n";
10062
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10063
        $sql = "SELECT * FROM ".$tbl_lp_item."
10064
                WHERE 
10065
                    c_id = $course_id AND 
10066
                    lp_id = ".$this->lp_id." AND 
10067
                    parent_item_id = 0
10068
                ORDER BY display_order ASC";
10069
        $res_zero = Database::query($sql);
10070
        $i = 0;
10071
10072
        while ($row_zero = Database::fetch_array($res_zero)) {
10073
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10074
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10075
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10076
                }
10077
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10078
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10079
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10080
            }
10081
        }
10082
        $return .= "\n";
10083
        $sql = "SELECT * FROM $tbl_lp_item
10084
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10085
        $res = Database::query($sql);
10086
        while ($row = Database::fetch_array($res)) {
10087
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10088
                           WHERE
10089
                                c_id = ".$course_id." AND
10090
                                parent_item_id = ".$row['iid']."
10091
                           ORDER BY display_order ASC";
10092
            $res_parent = Database::query($sql_parent);
10093
            $i = 0;
10094
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10095
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10096
10097
            while ($row_parent = Database::fetch_array($res_parent)) {
10098
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
10099
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10100
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10101
            }
10102
            $return .= "\n";
10103
        }
10104
10105
        $return .= "
10106
            function load_cbo(id) {
10107
                if (!id) {
10108
                    return false;
10109
                }
10110
            
10111
                var cbo = document.getElementById('previous');
10112
                for(var i = cbo.length - 1; i > 0; i--) {
10113
                    cbo.options[i] = null;
10114
                }
10115
            
10116
                var k=0;
10117
                for(var i = 1; i <= child_name[id].length; i++){
10118
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10119
                    option.style.paddingLeft = '40px';
10120
                    cbo.options[i] = option;
10121
                    k = i;
10122
                }
10123
            
10124
                cbo.options[k].selected = true;
10125
                $('#previous').selectpicker('refresh');
10126
            }";
10127
10128
        return $return;
10129
    }
10130
10131
    /**
10132
     * Display the form to allow moving an item.
10133
     *
10134
     * @param int $item_id Item ID
10135
     *
10136
     * @throws Exception
10137
     * @throws HTML_QuickForm_Error
10138
     *
10139
     * @return string HTML form
10140
     */
10141
    public function display_move_item($item_id)
10142
    {
10143
        $return = '';
10144
        if (is_numeric($item_id)) {
10145
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10146
10147
            $sql = "SELECT * FROM $tbl_lp_item
10148
                    WHERE iid = $item_id";
10149
            $res = Database::query($sql);
10150
            $row = Database::fetch_array($res);
10151
10152
            switch ($row['item_type']) {
10153
                case 'dir':
10154
                case 'asset':
10155
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10156
                    $return .= $this->display_item_form(
10157
                        $row['item_type'],
10158
                        get_lang('MoveCurrentChapter'),
10159
                        'move',
10160
                        $item_id,
10161
                        $row
10162
                    );
10163
                    break;
10164
                case TOOL_DOCUMENT:
10165
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10166
                    $return .= $this->display_document_form('move', $item_id, $row);
10167
                    break;
10168
                case TOOL_LINK:
10169
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10170
                    $return .= $this->display_link_form('move', $item_id, $row);
10171
                    break;
10172
                case TOOL_HOTPOTATOES:
10173
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10174
                    $return .= $this->display_link_form('move', $item_id, $row);
10175
                    break;
10176
                case TOOL_QUIZ:
10177
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10178
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10179
                    break;
10180
                case TOOL_STUDENTPUBLICATION:
10181
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10182
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10183
                    break;
10184
                case TOOL_FORUM:
10185
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10186
                    $return .= $this->display_forum_form('move', $item_id, $row);
10187
                    break;
10188
                case TOOL_THREAD:
10189
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10190
                    $return .= $this->display_forum_form('move', $item_id, $row);
10191
                    break;
10192
            }
10193
        }
10194
10195
        return $return;
10196
    }
10197
10198
    /**
10199
     * Return HTML form to allow prerequisites selection.
10200
     *
10201
     * @todo use FormValidator
10202
     *
10203
     * @param int Item ID
10204
     *
10205
     * @return string HTML form
10206
     */
10207
    public function display_item_prerequisites_form($item_id = 0)
10208
    {
10209
        $course_id = api_get_course_int_id();
10210
        $item_id = (int) $item_id;
10211
10212
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10213
10214
        /* Current prerequisite */
10215
        $sql = "SELECT * FROM $tbl_lp_item
10216
                WHERE iid = $item_id";
10217
        $result = Database::query($sql);
10218
        $row = Database::fetch_array($result);
10219
        $prerequisiteId = $row['prerequisite'];
10220
        $return = '<legend>';
10221
        $return .= get_lang('AddEditPrerequisites');
10222
        $return .= '</legend>';
10223
        $return .= '<form method="POST">';
10224
        $return .= '<div class="table-responsive">';
10225
        $return .= '<table class="table table-hover">';
10226
        $return .= '<thead>';
10227
        $return .= '<tr>';
10228
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10229
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10230
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10231
        $return .= '</tr>';
10232
        $return .= '</thead>';
10233
10234
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10235
        $return .= '<tbody>';
10236
        $return .= '<tr>';
10237
        $return .= '<td colspan="3">';
10238
        $return .= '<div class="radio learnpath"><label for="idNone">';
10239
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10240
        $return .= get_lang('None').'</label>';
10241
        $return .= '</div>';
10242
        $return .= '</tr>';
10243
10244
        $sql = "SELECT * FROM $tbl_lp_item
10245
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10246
        $result = Database::query($sql);
10247
        $arrLP = [];
10248
10249
        $selectedMinScore = [];
10250
        $selectedMaxScore = [];
10251
        $masteryScore = [];
10252
        while ($row = Database::fetch_array($result)) {
10253
            if ($row['iid'] == $item_id) {
10254
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10255
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10256
            }
10257
            $masteryScore[$row['iid']] = $row['mastery_score'];
10258
10259
            $arrLP[] = [
10260
                'id' => $row['iid'],
10261
                'item_type' => $row['item_type'],
10262
                'title' => $row['title'],
10263
                'ref' => $row['ref'],
10264
                'description' => $row['description'],
10265
                'parent_item_id' => $row['parent_item_id'],
10266
                'previous_item_id' => $row['previous_item_id'],
10267
                'next_item_id' => $row['next_item_id'],
10268
                'max_score' => $row['max_score'],
10269
                'min_score' => $row['min_score'],
10270
                'mastery_score' => $row['mastery_score'],
10271
                'prerequisite' => $row['prerequisite'],
10272
                'display_order' => $row['display_order'],
10273
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10274
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10275
            ];
10276
        }
10277
10278
        $this->tree_array($arrLP);
10279
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10280
        unset($this->arrMenu);
10281
10282
        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...
10283
            $item = $arrLP[$i];
10284
10285
            if ($item['id'] == $item_id) {
10286
                break;
10287
            }
10288
10289
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10290
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10291
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10292
10293
            $return .= '<tr>';
10294
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10295
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10296
            $return .= '<label for="id'.$item['id'].'">';
10297
            $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'].'" />';
10298
10299
            $icon_name = str_replace(' ', '', $item['item_type']);
10300
10301
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10302
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10303
            } else {
10304
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10305
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10306
                } else {
10307
                    $return .= Display::return_icon('folder_document.png');
10308
                }
10309
            }
10310
10311
            $return .= $item['title'].'</label>';
10312
            $return .= '</div>';
10313
            $return .= '</td>';
10314
10315
            if ($item['item_type'] == TOOL_QUIZ) {
10316
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10317
                $lpItemObj = new LpItem($course_id, $item['id']);
10318
                $exercise = new Exercise($course_id);
10319
                $exercise->read($lpItemObj->path);
10320
                $lpItemObj->max_score = $exercise->get_max_score();
10321
                $lpItemObj->update();
10322
                $item['max_score'] = $lpItemObj->max_score;
10323
10324
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10325
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10326
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10327
                }
10328
10329
                $return .= '<td>';
10330
                $return .= '<input 
10331
                    class="form-control" 
10332
                    size="4" maxlength="3" 
10333
                    name="min_'.$item['id'].'" 
10334
                    type="number" 
10335
                    min="0" 
10336
                    step="1" 
10337
                    max="'.$item['max_score'].'" 
10338
                    value="'.$selectedMinScoreValue.'" 
10339
                />';
10340
                $return .= '</td>';
10341
                $return .= '<td>';
10342
                $return .= '<input 
10343
                    class="form-control" 
10344
                    size="4" 
10345
                    maxlength="3" 
10346
                    name="max_'.$item['id'].'" 
10347
                    type="number" 
10348
                    min="0" 
10349
                    step="1" 
10350
                    max="'.$item['max_score'].'" 
10351
                    value="'.$selectedMaxScoreValue.'" 
10352
                />';
10353
                $return .= '</td>';
10354
            }
10355
10356
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10357
                $return .= '<td>';
10358
                $return .= '<input 
10359
                    size="4" 
10360
                    maxlength="3" 
10361
                    name="min_'.$item['id'].'" 
10362
                    type="number" 
10363
                    min="0" 
10364
                    step="1" 
10365
                    max="'.$item['max_score'].'" 
10366
                    value="'.$selectedMinScoreValue.'" 
10367
                />';
10368
                $return .= '</td>';
10369
                $return .= '<td>';
10370
                $return .= '<input 
10371
                    size="4" 
10372
                    maxlength="3" 
10373
                    name="max_'.$item['id'].'" 
10374
                    type="number" 
10375
                    min="0" 
10376
                    step="1" 
10377
                    max="'.$item['max_score'].'" 
10378
                    value="'.$selectedMaxScoreValue.'" 
10379
                />';
10380
                $return .= '</td>';
10381
            }
10382
            $return .= '</tr>';
10383
        }
10384
        $return .= '<tr>';
10385
        $return .= '</tr>';
10386
        $return .= '</tbody>';
10387
        $return .= '</table>';
10388
        $return .= '</div>';
10389
        $return .= '<div class="form-group">';
10390
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10391
            get_lang('ModifyPrerequisites').'</button>';
10392
        $return .= '</form>';
10393
10394
        return $return;
10395
    }
10396
10397
    /**
10398
     * Return HTML list to allow prerequisites selection for lp.
10399
     *
10400
     * @return string HTML form
10401
     */
10402
    public function display_lp_prerequisites_list()
10403
    {
10404
        $course_id = api_get_course_int_id();
10405
        $lp_id = $this->lp_id;
10406
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10407
10408
        // get current prerequisite
10409
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10410
        $result = Database::query($sql);
10411
        $row = Database::fetch_array($result);
10412
        $prerequisiteId = $row['prerequisite'];
10413
        $session_id = api_get_session_id();
10414
        $session_condition = api_get_session_condition($session_id, true, true);
10415
        $sql = "SELECT * FROM $tbl_lp
10416
                WHERE c_id = $course_id $session_condition
10417
                ORDER BY display_order ";
10418
        $rs = Database::query($sql);
10419
        $return = '';
10420
        $return .= '<select name="prerequisites" class="form-control">';
10421
        $return .= '<option value="0">'.get_lang('None').'</option>';
10422
        if (Database::num_rows($rs) > 0) {
10423
            while ($row = Database::fetch_array($rs)) {
10424
                if ($row['id'] == $lp_id) {
10425
                    continue;
10426
                }
10427
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10428
            }
10429
        }
10430
        $return .= '</select>';
10431
10432
        return $return;
10433
    }
10434
10435
    /**
10436
     * Creates a list with all the documents in it.
10437
     *
10438
     * @param bool $showInvisibleFiles
10439
     *
10440
     * @throws Exception
10441
     * @throws HTML_QuickForm_Error
10442
     *
10443
     * @return string
10444
     */
10445
    public function get_documents($showInvisibleFiles = false)
10446
    {
10447
        $course_info = api_get_course_info();
10448
        $sessionId = api_get_session_id();
10449
        $documentTree = DocumentManager::get_document_preview(
10450
            $course_info,
10451
            $this->lp_id,
10452
            null,
10453
            $sessionId,
10454
            true,
10455
            null,
10456
            null,
10457
            $showInvisibleFiles,
10458
            true
10459
        );
10460
10461
        $headers = [
10462
            get_lang('Files'),
10463
            get_lang('CreateTheDocument'),
10464
            get_lang('CreateReadOutText'),
10465
            get_lang('Upload'),
10466
        ];
10467
10468
        $form = new FormValidator(
10469
            'form_upload',
10470
            'POST',
10471
            $this->getCurrentBuildingModeURL(),
10472
            '',
10473
            ['enctype' => 'multipart/form-data']
10474
        );
10475
10476
        $folders = DocumentManager::get_all_document_folders(
10477
            api_get_course_info(),
10478
            0,
10479
            true
10480
        );
10481
10482
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10483
10484
        DocumentManager::build_directory_selector(
10485
            $folders,
10486
            $lpPathInfo['id'],
10487
            [],
10488
            true,
10489
            $form,
10490
            'directory_parent_id'
10491
        );
10492
10493
        $group = [
10494
            $form->createElement(
10495
                'radio',
10496
                'if_exists',
10497
                get_lang("UplWhatIfFileExists"),
10498
                get_lang('UplDoNothing'),
10499
                'nothing'
10500
            ),
10501
            $form->createElement(
10502
                'radio',
10503
                'if_exists',
10504
                null,
10505
                get_lang('UplOverwriteLong'),
10506
                'overwrite'
10507
            ),
10508
            $form->createElement(
10509
                'radio',
10510
                'if_exists',
10511
                null,
10512
                get_lang('UplRenameLong'),
10513
                'rename'
10514
            ),
10515
        ];
10516
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10517
10518
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10519
        $defaultFileExistsOption = 'rename';
10520
        if (!empty($fileExistsOption)) {
10521
            $defaultFileExistsOption = $fileExistsOption;
10522
        }
10523
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10524
10525
        // Check box options
10526
        $form->addElement(
10527
            'checkbox',
10528
            'unzip',
10529
            get_lang('Options'),
10530
            get_lang('Uncompress')
10531
        );
10532
10533
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10534
        $form->addMultipleUpload($url);
10535
        $new = $this->display_document_form('add', 0);
10536
        $frmReadOutText = $this->displayFrmReadOutText('add');
10537
        $tabs = Display::tabs(
10538
            $headers,
10539
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10540
            'subtab'
10541
        );
10542
10543
        return $tabs;
10544
    }
10545
10546
    /**
10547
     * Creates a list with all the exercises (quiz) in it.
10548
     *
10549
     * @return string
10550
     */
10551
    public function get_exercises()
10552
    {
10553
        $course_id = api_get_course_int_id();
10554
        $session_id = api_get_session_id();
10555
        $userInfo = api_get_user_info();
10556
10557
        // New for hotpotatoes.
10558
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10559
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10560
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10561
        $condition_session = api_get_session_condition($session_id, true, true);
10562
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10563
10564
        $activeCondition = ' active <> -1 ';
10565
        if ($setting) {
10566
            $activeCondition = ' active = 1 ';
10567
        }
10568
10569
        $sql_quiz = "SELECT * FROM $tbl_quiz
10570
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10571
                     ORDER BY title ASC";
10572
10573
        $sql_hot = "SELECT * FROM $tbl_doc
10574
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10575
                     ORDER BY id ASC";
10576
10577
        $res_quiz = Database::query($sql_quiz);
10578
        $res_hot = Database::query($sql_hot);
10579
10580
        $return = '<ul class="lp_resource">';
10581
        $return .= '<li class="lp_resource_element">';
10582
        $return .= Display::return_icon('new_exercice.png');
10583
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10584
            get_lang('NewExercise').'</a>';
10585
        $return .= '</li>';
10586
10587
        $previewIcon = Display::return_icon(
10588
            'preview_view.png',
10589
            get_lang('Preview')
10590
        );
10591
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10592
10593
        // Display hotpotatoes
10594
        while ($row_hot = Database::fetch_array($res_hot)) {
10595
            $link = Display::url(
10596
                $previewIcon,
10597
                $exerciseUrl.'&file='.$row_hot['path'],
10598
                ['target' => '_blank']
10599
            );
10600
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10601
            $return .= '<a class="moved" href="#">';
10602
            $return .= Display::return_icon(
10603
                'move_everywhere.png',
10604
                get_lang('Move'),
10605
                [],
10606
                ICON_SIZE_TINY
10607
            );
10608
            $return .= '</a> ';
10609
            $return .= Display::return_icon('hotpotatoes_s.png');
10610
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10611
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10612
            $return .= '</li>';
10613
        }
10614
10615
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10616
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10617
            $title = strip_tags(
10618
                api_html_entity_decode($row_quiz['title'])
10619
            );
10620
10621
            $link = Display::url(
10622
                $previewIcon,
10623
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10624
                ['target' => '_blank']
10625
            );
10626
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10627
            $return .= '<a class="moved" href="#">';
10628
            $return .= Display::return_icon(
10629
                'move_everywhere.png',
10630
                get_lang('Move'),
10631
                [],
10632
                ICON_SIZE_TINY
10633
            );
10634
            $return .= '</a> ';
10635
            $return .= Display::return_icon(
10636
                'quiz.png',
10637
                '',
10638
                [],
10639
                ICON_SIZE_TINY
10640
            );
10641
            $sessionStar = api_get_session_image(
10642
                $row_quiz['session_id'],
10643
                $userInfo['status']
10644
            );
10645
            $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.'">'.
10646
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar.
10647
                '</a>';
10648
10649
            $return .= '</li>';
10650
        }
10651
10652
        $return .= '</ul>';
10653
10654
        return $return;
10655
    }
10656
10657
    /**
10658
     * Creates a list with all the links in it.
10659
     *
10660
     * @return string
10661
     */
10662
    public function get_links()
10663
    {
10664
        $selfUrl = api_get_self();
10665
        $courseIdReq = api_get_cidreq();
10666
        $course = api_get_course_info();
10667
        $userInfo = api_get_user_info();
10668
10669
        $course_id = $course['real_id'];
10670
        $tbl_link = Database::get_course_table(TABLE_LINK);
10671
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10672
        $moveEverywhereIcon = Display::return_icon(
10673
            'move_everywhere.png',
10674
            get_lang('Move'),
10675
            [],
10676
            ICON_SIZE_TINY
10677
        );
10678
10679
        $session_id = api_get_session_id();
10680
        $condition_session = api_get_session_condition(
10681
            $session_id,
10682
            true,
10683
            true,
10684
            "link.session_id"
10685
        );
10686
10687
        $sql = "SELECT 
10688
                    link.id as link_id,
10689
                    link.title as link_title,
10690
                    link.session_id as link_session_id,
10691
                    link.category_id as category_id,
10692
                    link_category.category_title as category_title
10693
                FROM $tbl_link as link
10694
                LEFT JOIN $linkCategoryTable as link_category
10695
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10696
                WHERE link.c_id = ".$course_id." $condition_session
10697
                ORDER BY link_category.category_title ASC, link.title ASC";
10698
        $result = Database::query($sql);
10699
        $categorizedLinks = [];
10700
        $categories = [];
10701
10702
        while ($link = Database::fetch_array($result)) {
10703
            if (!$link['category_id']) {
10704
                $link['category_title'] = get_lang('Uncategorized');
10705
            }
10706
            $categories[$link['category_id']] = $link['category_title'];
10707
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10708
        }
10709
10710
        $linksHtmlCode =
10711
            '<script>
10712
            function toggle_tool(tool, id) {
10713
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10714
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10715
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10716
                } else {
10717
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10718
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10719
                }
10720
            }
10721
        </script>
10722
10723
        <ul class="lp_resource">
10724
            <li class="lp_resource_element">
10725
                '.Display::return_icon('linksnew.gif').'
10726
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10727
                get_lang('LinkAdd').'
10728
                </a>
10729
            </li>';
10730
10731
        foreach ($categorizedLinks as $categoryId => $links) {
10732
            $linkNodes = null;
10733
            foreach ($links as $key => $linkInfo) {
10734
                $title = $linkInfo['link_title'];
10735
                $linkSessionId = $linkInfo['link_session_id'];
10736
10737
                $link = Display::url(
10738
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10739
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10740
                    ['target' => '_blank']
10741
                );
10742
10743
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10744
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10745
                    $linkNodes .=
10746
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10747
                        <a class="moved" href="#">'.
10748
                            $moveEverywhereIcon.
10749
                        '</a>
10750
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10751
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10752
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10753
                        Security::remove_XSS($title).$sessionStar.$link.
10754
                        '</a>
10755
                    </li>';
10756
                }
10757
            }
10758
            $linksHtmlCode .=
10759
                '<li>
10760
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10761
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10762
                    align="absbottom" />
10763
                </a>
10764
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10765
            </li>
10766
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10767
        }
10768
        $linksHtmlCode .= '</ul>';
10769
10770
        return $linksHtmlCode;
10771
    }
10772
10773
    /**
10774
     * Creates a list with all the student publications in it.
10775
     *
10776
     * @return string
10777
     */
10778
    public function get_student_publications()
10779
    {
10780
        $return = '<ul class="lp_resource">';
10781
        $return .= '<li class="lp_resource_element">';
10782
        $return .= Display::return_icon('works_new.gif');
10783
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10784
            get_lang('AddAssignmentPage').'</a>';
10785
        $return .= '</li>';
10786
10787
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10788
        $works = getWorkListTeacher(0, 100, null, null, null);
10789
        if (!empty($works)) {
10790
            foreach ($works as $work) {
10791
                $link = Display::url(
10792
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10793
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10794
                    ['target' => '_blank']
10795
                );
10796
10797
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10798
                $return .= '<a class="moved" href="#">';
10799
                $return .= Display::return_icon(
10800
                    'move_everywhere.png',
10801
                    get_lang('Move'),
10802
                    [],
10803
                    ICON_SIZE_TINY
10804
                );
10805
                $return .= '</a> ';
10806
10807
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10808
                $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.'">'.
10809
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10810
                </a>';
10811
10812
                $return .= '</li>';
10813
            }
10814
        }
10815
10816
        $return .= '</ul>';
10817
10818
        return $return;
10819
    }
10820
10821
    /**
10822
     * Creates a list with all the forums in it.
10823
     *
10824
     * @return string
10825
     */
10826
    public function get_forums()
10827
    {
10828
        require_once '../forum/forumfunction.inc.php';
10829
        require_once '../forum/forumconfig.inc.php';
10830
10831
        $forumCategories = get_forum_categories();
10832
        $forumsInNoCategory = get_forums_in_category(0);
10833
        if (!empty($forumsInNoCategory)) {
10834
            $forumCategories = array_merge(
10835
                $forumCategories,
10836
                [
10837
                    [
10838
                        'cat_id' => 0,
10839
                        'session_id' => 0,
10840
                        'visibility' => 1,
10841
                        'cat_comment' => null,
10842
                    ],
10843
                ]
10844
            );
10845
        }
10846
10847
        $forumList = get_forums();
10848
        $a_forums = [];
10849
        foreach ($forumCategories as $forumCategory) {
10850
            // The forums in this category.
10851
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10852
            if (!empty($forumsInCategory)) {
10853
                foreach ($forumList as $forum) {
10854
                    if (isset($forum['forum_category']) &&
10855
                        $forum['forum_category'] == $forumCategory['cat_id']
10856
                    ) {
10857
                        $a_forums[] = $forum;
10858
                    }
10859
                }
10860
            }
10861
        }
10862
10863
        $return = '<ul class="lp_resource">';
10864
10865
        // First add link
10866
        $return .= '<li class="lp_resource_element">';
10867
        $return .= Display::return_icon('new_forum.png');
10868
        $return .= Display::url(
10869
            get_lang('CreateANewForum'),
10870
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10871
                'action' => 'add',
10872
                'content' => 'forum',
10873
                'lp_id' => $this->lp_id,
10874
            ]),
10875
            ['title' => get_lang('CreateANewForum')]
10876
        );
10877
        $return .= '</li>';
10878
10879
        $return .= '<script>
10880
            function toggle_forum(forum_id) {
10881
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10882
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10883
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10884
                } else {
10885
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10886
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10887
                }
10888
            }
10889
        </script>';
10890
10891
        foreach ($a_forums as $forum) {
10892
            if (!empty($forum['forum_id'])) {
10893
                $link = Display::url(
10894
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10895
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10896
                    ['target' => '_blank']
10897
                );
10898
10899
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10900
                $return .= '<a class="moved" href="#">';
10901
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10902
                $return .= ' </a>';
10903
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10904
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10905
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10906
                            </a>
10907
                            <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">'.
10908
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10909
10910
                $return .= '</li>';
10911
10912
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10913
                $a_threads = get_threads($forum['forum_id']);
10914
                if (is_array($a_threads)) {
10915
                    foreach ($a_threads as $thread) {
10916
                        $link = Display::url(
10917
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10918
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10919
                            ['target' => '_blank']
10920
                        );
10921
10922
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10923
                        $return .= '&nbsp;<a class="moved" href="#">';
10924
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10925
                        $return .= ' </a>';
10926
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10927
                        $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.'">'.
10928
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10929
                        $return .= '</li>';
10930
                    }
10931
                }
10932
                $return .= '</div>';
10933
            }
10934
        }
10935
        $return .= '</ul>';
10936
10937
        return $return;
10938
    }
10939
10940
    /**
10941
     * // TODO: The output encoding should be equal to the system encoding.
10942
     *
10943
     * Exports the learning path as a SCORM package. This is the main function that
10944
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10945
     * whole thing and returns the zip.
10946
     *
10947
     * This method needs to be called in PHP5, as it will fail with non-adequate
10948
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10949
     * you need to call it on a learnpath object.
10950
     *
10951
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10952
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10953
     * path has been modified, it should use the generic method here below.
10954
     *
10955
     * @return string Returns the zip package string, or null if error
10956
     */
10957
    public function scormExport()
10958
    {
10959
        api_set_more_memory_and_time_limits();
10960
10961
        $_course = api_get_course_info();
10962
        $course_id = $_course['real_id'];
10963
        // Create the zip handler (this will remain available throughout the method).
10964
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10965
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10966
        $temp_dir_short = uniqid('scorm_export', true);
10967
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10968
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10969
        $zip_folder = new PclZip($temp_zip_file);
10970
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10971
        $root_path = $main_path = api_get_path(SYS_PATH);
10972
        $files_cleanup = [];
10973
10974
        // Place to temporarily stash the zip file.
10975
        // create the temp dir if it doesn't exist
10976
        // or do a cleanup before creating the zip file.
10977
        if (!is_dir($temp_zip_dir)) {
10978
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10979
        } else {
10980
            // Cleanup: Check the temp dir for old files and delete them.
10981
            $handle = opendir($temp_zip_dir);
10982
            while (false !== ($file = readdir($handle))) {
10983
                if ($file != '.' && $file != '..') {
10984
                    unlink("$temp_zip_dir/$file");
10985
                }
10986
            }
10987
            closedir($handle);
10988
        }
10989
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10990
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10991
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10992
        ) {
10993
            // Remove the possible . at the end of the path.
10994
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10995
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10996
            mkdir(
10997
                $dest_path_to_scorm_folder,
10998
                api_get_permissions_for_new_directories(),
10999
                true
11000
            );
11001
            copyr(
11002
                $current_course_path.'/scorm/'.$this->path,
11003
                $dest_path_to_scorm_folder,
11004
                ['imsmanifest'],
11005
                $zip_files
11006
            );
11007
        }
11008
11009
        // Build a dummy imsmanifest structure.
11010
        // Do not add to the zip yet (we still need it).
11011
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11012
        // Aggregation Model official document, section "2.3 Content Packaging".
11013
        // We are going to build a UTF-8 encoded manifest.
11014
        // Later we will recode it to the desired (and supported) encoding.
11015
        $xmldoc = new DOMDocument('1.0');
11016
        $root = $xmldoc->createElement('manifest');
11017
        $root->setAttribute('identifier', 'SingleCourseManifest');
11018
        $root->setAttribute('version', '1.1');
11019
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11020
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11021
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11022
        $root->setAttribute(
11023
            'xsi:schemaLocation',
11024
            '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'
11025
        );
11026
        // Build mandatory sub-root container elements.
11027
        $metadata = $xmldoc->createElement('metadata');
11028
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11029
        $metadata->appendChild($md_schema);
11030
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11031
        $metadata->appendChild($md_schemaversion);
11032
        $root->appendChild($metadata);
11033
11034
        $organizations = $xmldoc->createElement('organizations');
11035
        $resources = $xmldoc->createElement('resources');
11036
11037
        // Build the only organization we will use in building our learnpaths.
11038
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11039
        $organization = $xmldoc->createElement('organization');
11040
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11041
        // To set the title of the SCORM entity (=organization), we take the name given
11042
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11043
        // learning path charset) as it is the encoding that defines how it is stored
11044
        // in the database. Then we convert it to HTML entities again as the "&" character
11045
        // alone is not authorized in XML (must be &amp;).
11046
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11047
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11048
        $organization->appendChild($org_title);
11049
        $folder_name = 'document';
11050
11051
        // Removes the learning_path/scorm_folder path when exporting see #4841
11052
        $path_to_remove = '';
11053
        $path_to_replace = '';
11054
        $result = $this->generate_lp_folder($_course);
11055
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11056
            $path_to_remove = 'document'.$result['dir'];
11057
            $path_to_replace = $folder_name.'/';
11058
        }
11059
11060
        // Fixes chamilo scorm exports
11061
        if ($this->ref === 'chamilo_scorm_export') {
11062
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11063
        }
11064
11065
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11066
        $link_updates = [];
11067
        $links_to_create = [];
11068
        foreach ($this->ordered_items as $index => $itemId) {
11069
            /** @var learnpathItem $item */
11070
            $item = $this->items[$itemId];
11071
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11072
                // Get included documents from this item.
11073
                if ($item->type === 'sco') {
11074
                    $inc_docs = $item->get_resources_from_source(
11075
                        null,
11076
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11077
                    );
11078
                } else {
11079
                    $inc_docs = $item->get_resources_from_source();
11080
                }
11081
11082
                // Give a child element <item> to the <organization> element.
11083
                $my_item_id = $item->get_id();
11084
                $my_item = $xmldoc->createElement('item');
11085
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11086
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11087
                $my_item->setAttribute('isvisible', 'true');
11088
                // Give a child element <title> to the <item> element.
11089
                $my_title = $xmldoc->createElement(
11090
                    'title',
11091
                    htmlspecialchars(
11092
                        api_utf8_encode($item->get_title()),
11093
                        ENT_QUOTES,
11094
                        'UTF-8'
11095
                    )
11096
                );
11097
                $my_item->appendChild($my_title);
11098
                // Give a child element <adlcp:prerequisites> to the <item> element.
11099
                $my_prereqs = $xmldoc->createElement(
11100
                    'adlcp:prerequisites',
11101
                    $this->get_scorm_prereq_string($my_item_id)
11102
                );
11103
                $my_prereqs->setAttribute('type', 'aicc_script');
11104
                $my_item->appendChild($my_prereqs);
11105
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11106
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11107
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11108
                //$xmldoc->createElement('adlcp:timelimitaction','');
11109
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11110
                //$xmldoc->createElement('adlcp:datafromlms','');
11111
                // Give a child element <adlcp:masteryscore> to the <item> element.
11112
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11113
                $my_item->appendChild($my_masteryscore);
11114
11115
                // Attach this item to the organization element or hits parent if there is one.
11116
                if (!empty($item->parent) && $item->parent != 0) {
11117
                    $children = $organization->childNodes;
11118
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11119
                    if (is_object($possible_parent)) {
11120
                        $possible_parent->appendChild($my_item);
11121
                    } else {
11122
                        if ($this->debug > 0) {
11123
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11124
                        }
11125
                    }
11126
                } else {
11127
                    if ($this->debug > 0) {
11128
                        error_log('No parent');
11129
                    }
11130
                    $organization->appendChild($my_item);
11131
                }
11132
11133
                // Get the path of the file(s) from the course directory root.
11134
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11135
                $my_xml_file_path = $my_file_path;
11136
                if (!empty($path_to_remove)) {
11137
                    // From docs
11138
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11139
11140
                    // From quiz
11141
                    if ($this->ref === 'chamilo_scorm_export') {
11142
                        $path_to_remove = 'scorm/'.$this->path.'/';
11143
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11144
                    }
11145
                }
11146
11147
                $my_sub_dir = dirname($my_file_path);
11148
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11149
                $my_xml_sub_dir = $my_sub_dir;
11150
                // Give a <resource> child to the <resources> element
11151
                $my_resource = $xmldoc->createElement('resource');
11152
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11153
                $my_resource->setAttribute('type', 'webcontent');
11154
                $my_resource->setAttribute('href', $my_xml_file_path);
11155
                // adlcp:scormtype can be either 'sco' or 'asset'.
11156
                if ($item->type === 'sco') {
11157
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11158
                } else {
11159
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11160
                }
11161
                // xml:base is the base directory to find the files declared in this resource.
11162
                $my_resource->setAttribute('xml:base', '');
11163
                // Give a <file> child to the <resource> element.
11164
                $my_file = $xmldoc->createElement('file');
11165
                $my_file->setAttribute('href', $my_xml_file_path);
11166
                $my_resource->appendChild($my_file);
11167
11168
                // Dependency to other files - not yet supported.
11169
                $i = 1;
11170
                if ($inc_docs) {
11171
                    foreach ($inc_docs as $doc_info) {
11172
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11173
                            continue;
11174
                        }
11175
                        $my_dep = $xmldoc->createElement('resource');
11176
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11177
                        $my_dep->setAttribute('identifier', $res_id);
11178
                        $my_dep->setAttribute('type', 'webcontent');
11179
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11180
                        $my_dep_file = $xmldoc->createElement('file');
11181
                        // Check type of URL.
11182
                        if ($doc_info[1] == 'remote') {
11183
                            // Remote file. Save url as is.
11184
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11185
                            $my_dep->setAttribute('xml:base', '');
11186
                        } elseif ($doc_info[1] === 'local') {
11187
                            switch ($doc_info[2]) {
11188
                                case 'url':
11189
                                    // Local URL - save path as url for now, don't zip file.
11190
                                    $abs_path = api_get_path(SYS_PATH).
11191
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11192
                                    $current_dir = dirname($abs_path);
11193
                                    $current_dir = str_replace('\\', '/', $current_dir);
11194
                                    $file_path = realpath($abs_path);
11195
                                    $file_path = str_replace('\\', '/', $file_path);
11196
                                    $my_dep_file->setAttribute('href', $file_path);
11197
                                    $my_dep->setAttribute('xml:base', '');
11198
                                    if (strstr($file_path, $main_path) !== false) {
11199
                                        // The calculated real path is really inside Chamilo's root path.
11200
                                        // Reduce file path to what's under the DocumentRoot.
11201
                                        $replace = $file_path;
11202
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11203
                                        $destinationFile = $file_path;
11204
11205
                                        if (strstr($file_path, 'upload/users') !== false) {
11206
                                            $pos = strpos($file_path, 'my_files/');
11207
                                            if ($pos !== false) {
11208
                                                $onlyDirectory = str_replace(
11209
                                                    'upload/users/',
11210
                                                    '',
11211
                                                    substr($file_path, $pos, strlen($file_path))
11212
                                                );
11213
                                            }
11214
                                            $replace = $onlyDirectory;
11215
                                            $destinationFile = $replace;
11216
                                        }
11217
                                        $zip_files_abs[] = $file_path;
11218
                                        $link_updates[$my_file_path][] = [
11219
                                            'orig' => $doc_info[0],
11220
                                            'dest' => $destinationFile,
11221
                                            'replace' => $replace,
11222
                                        ];
11223
                                        $my_dep_file->setAttribute('href', $file_path);
11224
                                        $my_dep->setAttribute('xml:base', '');
11225
                                    } elseif (empty($file_path)) {
11226
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11227
                                        $file_path = str_replace('//', '/', $file_path);
11228
                                        if (file_exists($file_path)) {
11229
                                            // We get the relative path.
11230
                                            $file_path = substr($file_path, strlen($current_dir));
11231
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11232
                                            $link_updates[$my_file_path][] = [
11233
                                                'orig' => $doc_info[0],
11234
                                                'dest' => $file_path,
11235
                                            ];
11236
                                            $my_dep_file->setAttribute('href', $file_path);
11237
                                            $my_dep->setAttribute('xml:base', '');
11238
                                        }
11239
                                    }
11240
                                    break;
11241
                                case 'abs':
11242
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11243
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11244
                                    $my_dep->setAttribute('xml:base', '');
11245
11246
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11247
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11248
                                    $abs_img_path_without_subdir = $doc_info[0];
11249
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11250
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11251
                                    if ($pos === 0) {
11252
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11253
                                    }
11254
11255
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11256
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11257
11258
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11259
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11260
                                    // Check if the current document is in that path.
11261
                                    if (strstr($file_path, $cur_path) !== false) {
11262
                                        $destinationFile = substr($file_path, strlen($cur_path));
11263
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11264
11265
                                        $fileToTest = $cur_path.$my_file_path;
11266
                                        if (!empty($path_to_remove)) {
11267
                                            $fileToTest = str_replace(
11268
                                                $path_to_remove.'/',
11269
                                                $path_to_replace,
11270
                                                $cur_path.$my_file_path
11271
                                            );
11272
                                        }
11273
11274
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11275
11276
                                        // Put the current document in the zip (this array is the array
11277
                                        // that will manage documents already in the course folder - relative).
11278
                                        $zip_files[] = $filePathNoCoursePart;
11279
                                        // Update the links to the current document in the
11280
                                        // containing document (make them relative).
11281
                                        $link_updates[$my_file_path][] = [
11282
                                            'orig' => $doc_info[0],
11283
                                            'dest' => $destinationFile,
11284
                                            'replace' => $relative_path,
11285
                                        ];
11286
11287
                                        $my_dep_file->setAttribute('href', $file_path);
11288
                                        $my_dep->setAttribute('xml:base', '');
11289
                                    } elseif (strstr($file_path, $main_path) !== false) {
11290
                                        // The calculated real path is really inside Chamilo's root path.
11291
                                        // Reduce file path to what's under the DocumentRoot.
11292
                                        $file_path = substr($file_path, strlen($root_path));
11293
                                        $zip_files_abs[] = $file_path;
11294
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11295
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11296
                                        $my_dep->setAttribute('xml:base', '');
11297
                                    } elseif (empty($file_path)) {
11298
                                        // Probably this is an image inside "/main" directory
11299
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11300
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11301
11302
                                        if (file_exists($file_path)) {
11303
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11304
                                                // We get the relative path.
11305
                                                $pos = strpos($file_path, 'main/default_course_document/');
11306
                                                if ($pos !== false) {
11307
                                                    $onlyDirectory = str_replace(
11308
                                                        'main/default_course_document/',
11309
                                                        '',
11310
                                                        substr($file_path, $pos, strlen($file_path))
11311
                                                    );
11312
                                                }
11313
11314
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11315
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11316
                                                $zip_files_abs[] = $fileAbs;
11317
                                                $link_updates[$my_file_path][] = [
11318
                                                    'orig' => $doc_info[0],
11319
                                                    'dest' => $destinationFile,
11320
                                                ];
11321
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11322
                                                $my_dep->setAttribute('xml:base', '');
11323
                                            }
11324
                                        }
11325
                                    }
11326
                                    break;
11327
                                case 'rel':
11328
                                    // Path relative to the current document.
11329
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11330
                                    if (substr($doc_info[0], 0, 2) === '..') {
11331
                                        // Relative path going up.
11332
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11333
                                        $current_dir = str_replace('\\', '/', $current_dir);
11334
                                        $file_path = realpath($current_dir.$doc_info[0]);
11335
                                        $file_path = str_replace('\\', '/', $file_path);
11336
                                        if (strstr($file_path, $main_path) !== false) {
11337
                                            // The calculated real path is really inside Chamilo's root path.
11338
                                            // Reduce file path to what's under the DocumentRoot.
11339
                                            $file_path = substr($file_path, strlen($root_path));
11340
                                            $zip_files_abs[] = $file_path;
11341
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11342
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11343
                                            $my_dep->setAttribute('xml:base', '');
11344
                                        }
11345
                                    } else {
11346
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11347
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11348
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11349
                                    }
11350
                                    break;
11351
                                default:
11352
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11353
                                    $my_dep->setAttribute('xml:base', '');
11354
                                    break;
11355
                            }
11356
                        }
11357
                        $my_dep->appendChild($my_dep_file);
11358
                        $resources->appendChild($my_dep);
11359
                        $dependency = $xmldoc->createElement('dependency');
11360
                        $dependency->setAttribute('identifierref', $res_id);
11361
                        $my_resource->appendChild($dependency);
11362
                        $i++;
11363
                    }
11364
                }
11365
                $resources->appendChild($my_resource);
11366
                $zip_files[] = $my_file_path;
11367
            } else {
11368
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11369
                switch ($item->type) {
11370
                    case TOOL_LINK:
11371
                        $my_item = $xmldoc->createElement('item');
11372
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11373
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11374
                        $my_item->setAttribute('isvisible', 'true');
11375
                        // Give a child element <title> to the <item> element.
11376
                        $my_title = $xmldoc->createElement(
11377
                            'title',
11378
                            htmlspecialchars(
11379
                                api_utf8_encode($item->get_title()),
11380
                                ENT_QUOTES,
11381
                                'UTF-8'
11382
                            )
11383
                        );
11384
                        $my_item->appendChild($my_title);
11385
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11386
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11387
                        $my_prereqs->setAttribute('type', 'aicc_script');
11388
                        $my_item->appendChild($my_prereqs);
11389
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11390
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11391
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11392
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11393
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11394
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11395
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11396
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11397
                        $my_item->appendChild($my_masteryscore);
11398
11399
                        // Attach this item to the organization element or its parent if there is one.
11400
                        if (!empty($item->parent) && $item->parent != 0) {
11401
                            $children = $organization->childNodes;
11402
                            for ($i = 0; $i < $children->length; $i++) {
11403
                                $item_temp = $children->item($i);
11404
                                if ($item_temp->nodeName == 'item') {
11405
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11406
                                        $item_temp->appendChild($my_item);
11407
                                    }
11408
                                }
11409
                            }
11410
                        } else {
11411
                            $organization->appendChild($my_item);
11412
                        }
11413
11414
                        $my_file_path = 'link_'.$item->get_id().'.html';
11415
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11416
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11417
                        $rs = Database::query($sql);
11418
                        if ($link = Database::fetch_array($rs)) {
11419
                            $url = $link['url'];
11420
                            $title = stripslashes($link['title']);
11421
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11422
                            $my_xml_file_path = $my_file_path;
11423
                            $my_sub_dir = dirname($my_file_path);
11424
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11425
                            $my_xml_sub_dir = $my_sub_dir;
11426
                            // Give a <resource> child to the <resources> element.
11427
                            $my_resource = $xmldoc->createElement('resource');
11428
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11429
                            $my_resource->setAttribute('type', 'webcontent');
11430
                            $my_resource->setAttribute('href', $my_xml_file_path);
11431
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11432
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11433
                            // xml:base is the base directory to find the files declared in this resource.
11434
                            $my_resource->setAttribute('xml:base', '');
11435
                            // give a <file> child to the <resource> element.
11436
                            $my_file = $xmldoc->createElement('file');
11437
                            $my_file->setAttribute('href', $my_xml_file_path);
11438
                            $my_resource->appendChild($my_file);
11439
                            $resources->appendChild($my_resource);
11440
                        }
11441
                        break;
11442
                    case TOOL_QUIZ:
11443
                        $exe_id = $item->path;
11444
                        // Should be using ref when everything will be cleaned up in this regard.
11445
                        $exe = new Exercise();
11446
                        $exe->read($exe_id);
11447
                        $my_item = $xmldoc->createElement('item');
11448
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11449
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11450
                        $my_item->setAttribute('isvisible', 'true');
11451
                        // Give a child element <title> to the <item> element.
11452
                        $my_title = $xmldoc->createElement(
11453
                            'title',
11454
                            htmlspecialchars(
11455
                                api_utf8_encode($item->get_title()),
11456
                                ENT_QUOTES,
11457
                                'UTF-8'
11458
                            )
11459
                        );
11460
                        $my_item->appendChild($my_title);
11461
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11462
                        $my_item->appendChild($my_max_score);
11463
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11464
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11465
                        $my_prereqs->setAttribute('type', 'aicc_script');
11466
                        $my_item->appendChild($my_prereqs);
11467
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11468
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11469
                        $my_item->appendChild($my_masteryscore);
11470
11471
                        // Attach this item to the organization element or hits parent if there is one.
11472
                        if (!empty($item->parent) && $item->parent != 0) {
11473
                            $children = $organization->childNodes;
11474
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11475
                            if ($possible_parent) {
11476
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11477
                                    $possible_parent->appendChild($my_item);
11478
                                }
11479
                            }
11480
                        } else {
11481
                            $organization->appendChild($my_item);
11482
                        }
11483
11484
                        // Get the path of the file(s) from the course directory root
11485
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11486
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11487
                        // Write the contents of the exported exercise into a (big) html file
11488
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11489
                        $scormExercise = new ScormExercise($exe, true);
11490
                        $contents = $scormExercise->export();
11491
11492
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11493
                        $res = file_put_contents($tmp_file_path, $contents);
11494
                        if ($res === false) {
11495
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11496
                        }
11497
                        $files_cleanup[] = $tmp_file_path;
11498
                        $my_xml_file_path = $my_file_path;
11499
                        $my_sub_dir = dirname($my_file_path);
11500
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11501
                        $my_xml_sub_dir = $my_sub_dir;
11502
                        // Give a <resource> child to the <resources> element.
11503
                        $my_resource = $xmldoc->createElement('resource');
11504
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11505
                        $my_resource->setAttribute('type', 'webcontent');
11506
                        $my_resource->setAttribute('href', $my_xml_file_path);
11507
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11508
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11509
                        // xml:base is the base directory to find the files declared in this resource.
11510
                        $my_resource->setAttribute('xml:base', '');
11511
                        // Give a <file> child to the <resource> element.
11512
                        $my_file = $xmldoc->createElement('file');
11513
                        $my_file->setAttribute('href', $my_xml_file_path);
11514
                        $my_resource->appendChild($my_file);
11515
11516
                        // Get included docs.
11517
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11518
11519
                        // Dependency to other files - not yet supported.
11520
                        $i = 1;
11521
                        foreach ($inc_docs as $doc_info) {
11522
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11523
                                continue;
11524
                            }
11525
                            $my_dep = $xmldoc->createElement('resource');
11526
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11527
                            $my_dep->setAttribute('identifier', $res_id);
11528
                            $my_dep->setAttribute('type', 'webcontent');
11529
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11530
                            $my_dep_file = $xmldoc->createElement('file');
11531
                            // Check type of URL.
11532
                            if ($doc_info[1] == 'remote') {
11533
                                // Remote file. Save url as is.
11534
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11535
                                $my_dep->setAttribute('xml:base', '');
11536
                            } elseif ($doc_info[1] == 'local') {
11537
                                switch ($doc_info[2]) {
11538
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11539
                                        // Save file but as local file (retrieve from URL).
11540
                                        $abs_path = api_get_path(SYS_PATH).
11541
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11542
                                        $current_dir = dirname($abs_path);
11543
                                        $current_dir = str_replace('\\', '/', $current_dir);
11544
                                        $file_path = realpath($abs_path);
11545
                                        $file_path = str_replace('\\', '/', $file_path);
11546
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11547
                                        $my_dep->setAttribute('xml:base', '');
11548
                                        if (strstr($file_path, $main_path) !== false) {
11549
                                            // The calculated real path is really inside the chamilo root path.
11550
                                            // Reduce file path to what's under the DocumentRoot.
11551
                                            $file_path = substr($file_path, strlen($root_path));
11552
                                            $zip_files_abs[] = $file_path;
11553
                                            $link_updates[$my_file_path][] = [
11554
                                                'orig' => $doc_info[0],
11555
                                                'dest' => 'document/'.$file_path,
11556
                                            ];
11557
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11558
                                            $my_dep->setAttribute('xml:base', '');
11559
                                        } elseif (empty($file_path)) {
11560
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11561
                                            $file_path = str_replace('//', '/', $file_path);
11562
                                            if (file_exists($file_path)) {
11563
                                                $file_path = substr($file_path, strlen($current_dir));
11564
                                                // We get the relative path.
11565
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11566
                                                $link_updates[$my_file_path][] = [
11567
                                                    'orig' => $doc_info[0],
11568
                                                    'dest' => 'document/'.$file_path,
11569
                                                ];
11570
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11571
                                                $my_dep->setAttribute('xml:base', '');
11572
                                            }
11573
                                        }
11574
                                        break;
11575
                                    case 'abs':
11576
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11577
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11578
                                        $current_dir = str_replace('\\', '/', $current_dir);
11579
                                        $file_path = realpath($doc_info[0]);
11580
                                        $file_path = str_replace('\\', '/', $file_path);
11581
                                        $my_dep_file->setAttribute('href', $file_path);
11582
                                        $my_dep->setAttribute('xml:base', '');
11583
11584
                                        if (strstr($file_path, $main_path) !== false) {
11585
                                            // The calculated real path is really inside the chamilo root path.
11586
                                            // Reduce file path to what's under the DocumentRoot.
11587
                                            $file_path = substr($file_path, strlen($root_path));
11588
                                            $zip_files_abs[] = $file_path;
11589
                                            $link_updates[$my_file_path][] = [
11590
                                                'orig' => $doc_info[0],
11591
                                                'dest' => $file_path,
11592
                                            ];
11593
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11594
                                            $my_dep->setAttribute('xml:base', '');
11595
                                        } elseif (empty($file_path)) {
11596
                                            $docSysPartPath = str_replace(
11597
                                                api_get_path(REL_COURSE_PATH),
11598
                                                '',
11599
                                                $doc_info[0]
11600
                                            );
11601
11602
                                            $docSysPartPathNoCourseCode = str_replace(
11603
                                                $_course['directory'].'/',
11604
                                                '',
11605
                                                $docSysPartPath
11606
                                            );
11607
11608
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11609
                                            if (file_exists($docSysPath)) {
11610
                                                $file_path = $docSysPartPathNoCourseCode;
11611
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11612
                                                $link_updates[$my_file_path][] = [
11613
                                                    'orig' => $doc_info[0],
11614
                                                    'dest' => $file_path,
11615
                                                ];
11616
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11617
                                                $my_dep->setAttribute('xml:base', '');
11618
                                            }
11619
                                        }
11620
                                        break;
11621
                                    case 'rel':
11622
                                        // Path relative to the current document. Save xml:base as current document's
11623
                                        // directory and save file in zip as subdir.file_path
11624
                                        if (substr($doc_info[0], 0, 2) === '..') {
11625
                                            // Relative path going up.
11626
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11627
                                            $current_dir = str_replace('\\', '/', $current_dir);
11628
                                            $file_path = realpath($current_dir.$doc_info[0]);
11629
                                            $file_path = str_replace('\\', '/', $file_path);
11630
                                            //error_log($file_path.' <-> '.$main_path, 0);
11631
                                            if (strstr($file_path, $main_path) !== false) {
11632
                                                // The calculated real path is really inside Chamilo's root path.
11633
                                                // Reduce file path to what's under the DocumentRoot.
11634
11635
                                                $file_path = substr($file_path, strlen($root_path));
11636
                                                $file_path_dest = $file_path;
11637
11638
                                                // File path is courses/CHAMILO/document/....
11639
                                                $info_file_path = explode('/', $file_path);
11640
                                                if ($info_file_path[0] == 'courses') {
11641
                                                    // Add character "/" in file path.
11642
                                                    $file_path_dest = 'document/'.$file_path;
11643
                                                }
11644
                                                $zip_files_abs[] = $file_path;
11645
11646
                                                $link_updates[$my_file_path][] = [
11647
                                                    'orig' => $doc_info[0],
11648
                                                    'dest' => $file_path_dest,
11649
                                                ];
11650
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11651
                                                $my_dep->setAttribute('xml:base', '');
11652
                                            }
11653
                                        } else {
11654
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11655
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11656
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11657
                                        }
11658
                                        break;
11659
                                    default:
11660
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11661
                                        $my_dep->setAttribute('xml:base', '');
11662
                                        break;
11663
                                }
11664
                            }
11665
                            $my_dep->appendChild($my_dep_file);
11666
                            $resources->appendChild($my_dep);
11667
                            $dependency = $xmldoc->createElement('dependency');
11668
                            $dependency->setAttribute('identifierref', $res_id);
11669
                            $my_resource->appendChild($dependency);
11670
                            $i++;
11671
                        }
11672
                        $resources->appendChild($my_resource);
11673
                        $zip_files[] = $my_file_path;
11674
                        break;
11675
                    default:
11676
                        // Get the path of the file(s) from the course directory root
11677
                        $my_file_path = 'non_exportable.html';
11678
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11679
                        $my_xml_file_path = $my_file_path;
11680
                        $my_sub_dir = dirname($my_file_path);
11681
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11682
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11683
                        $my_xml_sub_dir = $my_sub_dir;
11684
                        // Give a <resource> child to the <resources> element.
11685
                        $my_resource = $xmldoc->createElement('resource');
11686
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11687
                        $my_resource->setAttribute('type', 'webcontent');
11688
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11689
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11690
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11691
                        // xml:base is the base directory to find the files declared in this resource.
11692
                        $my_resource->setAttribute('xml:base', '');
11693
                        // Give a <file> child to the <resource> element.
11694
                        $my_file = $xmldoc->createElement('file');
11695
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11696
                        $my_resource->appendChild($my_file);
11697
                        $resources->appendChild($my_resource);
11698
                        break;
11699
                }
11700
            }
11701
        }
11702
        $organizations->appendChild($organization);
11703
        $root->appendChild($organizations);
11704
        $root->appendChild($resources);
11705
        $xmldoc->appendChild($root);
11706
11707
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11708
11709
        // then add the file to the zip, then destroy the file (this is done automatically).
11710
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11711
        foreach ($zip_files as $file_path) {
11712
            if (empty($file_path)) {
11713
                continue;
11714
            }
11715
11716
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11717
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11718
11719
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11720
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11721
            }
11722
11723
            $this->create_path($dest_file);
11724
            @copy($filePath, $dest_file);
11725
11726
            // Check if the file needs a link update.
11727
            if (in_array($file_path, array_keys($link_updates))) {
11728
                $string = file_get_contents($dest_file);
11729
                unlink($dest_file);
11730
                foreach ($link_updates[$file_path] as $old_new) {
11731
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11732
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11733
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11734
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11735
                    if (substr($old_new['dest'], -3) === 'flv' &&
11736
                        substr($old_new['dest'], 0, 5) === 'main/'
11737
                    ) {
11738
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11739
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11740
                        substr($old_new['dest'], 0, 6) === 'video/'
11741
                    ) {
11742
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11743
                    }
11744
11745
                    // Fix to avoid problems with default_course_document
11746
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11747
                        $newDestination = $old_new['dest'];
11748
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11749
                            $newDestination = $old_new['replace'];
11750
                        }
11751
                    } else {
11752
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11753
                    }
11754
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11755
11756
                    // Add files inside the HTMLs
11757
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11758
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11759
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11760
                        copy(
11761
                            $sys_course_path.$new_path,
11762
                            $destinationFile
11763
                        );
11764
                    }
11765
                }
11766
                file_put_contents($dest_file, $string);
11767
            }
11768
11769
            if (file_exists($filePath) && $copyAll) {
11770
                $extension = $this->get_extension($filePath);
11771
                if (in_array($extension, ['html', 'html'])) {
11772
                    $containerOrigin = dirname($filePath);
11773
                    $containerDestination = dirname($dest_file);
11774
11775
                    $finder = new Finder();
11776
                    $finder->files()->in($containerOrigin)
11777
                        ->notName('*_DELETED_*')
11778
                        ->exclude('share_folder')
11779
                        ->exclude('chat_files')
11780
                        ->exclude('certificates')
11781
                    ;
11782
11783
                    if (is_dir($containerOrigin) &&
11784
                        is_dir($containerDestination)
11785
                    ) {
11786
                        $fs = new Filesystem();
11787
                        $fs->mirror(
11788
                            $containerOrigin,
11789
                            $containerDestination,
11790
                            $finder
11791
                        );
11792
                    }
11793
                }
11794
            }
11795
        }
11796
11797
        foreach ($zip_files_abs as $file_path) {
11798
            if (empty($file_path)) {
11799
                continue;
11800
            }
11801
11802
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11803
                continue;
11804
            }
11805
11806
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11807
            if (strstr($file_path, 'upload/users') !== false) {
11808
                $pos = strpos($file_path, 'my_files/');
11809
                if ($pos !== false) {
11810
                    $onlyDirectory = str_replace(
11811
                        'upload/users/',
11812
                        '',
11813
                        substr($file_path, $pos, strlen($file_path))
11814
                    );
11815
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11816
                }
11817
            }
11818
11819
            if (strstr($file_path, 'default_course_document/') !== false) {
11820
                $replace = str_replace('/main', '', $file_path);
11821
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11822
            }
11823
11824
            if (empty($dest_file)) {
11825
                continue;
11826
            }
11827
11828
            $this->create_path($dest_file);
11829
            copy($main_path.$file_path, $dest_file);
11830
            // Check if the file needs a link update.
11831
            if (in_array($file_path, array_keys($link_updates))) {
11832
                $string = file_get_contents($dest_file);
11833
                unlink($dest_file);
11834
                foreach ($link_updates[$file_path] as $old_new) {
11835
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11836
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11837
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11838
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11839
                    if (substr($old_new['dest'], -3) == 'flv' &&
11840
                        substr($old_new['dest'], 0, 5) == 'main/'
11841
                    ) {
11842
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11843
                    }
11844
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11845
                }
11846
                file_put_contents($dest_file, $string);
11847
            }
11848
        }
11849
11850
        if (is_array($links_to_create)) {
11851
            foreach ($links_to_create as $file => $link) {
11852
                $content = '<!DOCTYPE html><head>
11853
                            <meta charset="'.api_get_language_isocode().'" />
11854
                            <title>'.$link['title'].'</title>
11855
                            </head>
11856
                            <body dir="'.api_get_text_direction().'">
11857
                            <div style="text-align:center">
11858
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11859
                            </body>
11860
                            </html>';
11861
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11862
            }
11863
        }
11864
11865
        // Add non exportable message explanation.
11866
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11867
        $file_content = '<!DOCTYPE html><head>
11868
                        <meta charset="'.api_get_language_isocode().'" />
11869
                        <title>'.$lang_not_exportable.'</title>
11870
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11871
                        </head>
11872
                        <body dir="'.api_get_text_direction().'">';
11873
        $file_content .=
11874
            <<<EOD
11875
                    <style>
11876
            .error-message {
11877
                font-family: arial, verdana, helvetica, sans-serif;
11878
                border-width: 1px;
11879
                border-style: solid;
11880
                left: 50%;
11881
                margin: 10px auto;
11882
                min-height: 30px;
11883
                padding: 5px;
11884
                right: 50%;
11885
                width: 500px;
11886
                background-color: #FFD1D1;
11887
                border-color: #FF0000;
11888
                color: #000;
11889
            }
11890
        </style>
11891
    <body>
11892
        <div class="error-message">
11893
            $lang_not_exportable
11894
        </div>
11895
    </body>
11896
</html>
11897
EOD;
11898
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11899
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11900
        }
11901
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11902
11903
        // Add the extra files that go along with a SCORM package.
11904
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11905
11906
        $fs = new Filesystem();
11907
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11908
11909
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11910
        $manifest = @$xmldoc->saveXML();
11911
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11912
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11913
        $zip_folder->add(
11914
            $archivePath.'/'.$temp_dir_short,
11915
            PCLZIP_OPT_REMOVE_PATH,
11916
            $archivePath.'/'.$temp_dir_short.'/'
11917
        );
11918
11919
        // Clean possible temporary files.
11920
        foreach ($files_cleanup as $file) {
11921
            $res = unlink($file);
11922
            if ($res === false) {
11923
                error_log(
11924
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11925
                    0
11926
                );
11927
            }
11928
        }
11929
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11930
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11931
    }
11932
11933
    /**
11934
     * @param int $lp_id
11935
     *
11936
     * @return bool
11937
     */
11938
    public function scorm_export_to_pdf($lp_id)
11939
    {
11940
        $lp_id = (int) $lp_id;
11941
        $files_to_export = [];
11942
        $course_data = api_get_course_info($this->cc);
11943
        if (!empty($course_data)) {
11944
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11945
            $list = self::get_flat_ordered_items_list($lp_id);
11946
            if (!empty($list)) {
11947
                foreach ($list as $item_id) {
11948
                    $item = $this->items[$item_id];
11949
                    switch ($item->type) {
11950
                        case 'document':
11951
                            //Getting documents from a LP with chamilo documents
11952
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11953
                            // Try loading document from the base course.
11954
                            if (empty($file_data) && !empty($sessionId)) {
11955
                                $file_data = DocumentManager::get_document_data_by_id(
11956
                                    $item->path,
11957
                                    $this->cc,
11958
                                    false,
11959
                                    0
11960
                                );
11961
                            }
11962
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11963
                            if (file_exists($file_path)) {
11964
                                $files_to_export[] = [
11965
                                    'title' => $item->get_title(),
11966
                                    'path' => $file_path,
11967
                                ];
11968
                            }
11969
                            break;
11970
                        case 'asset': //commes from a scorm package generated by chamilo
11971
                        case 'sco':
11972
                            $file_path = $scorm_path.'/'.$item->path;
11973
                            if (file_exists($file_path)) {
11974
                                $files_to_export[] = [
11975
                                    'title' => $item->get_title(),
11976
                                    'path' => $file_path,
11977
                                ];
11978
                            }
11979
                            break;
11980
                        case 'dir':
11981
                            $files_to_export[] = [
11982
                                'title' => $item->get_title(),
11983
                                'path' => null,
11984
                            ];
11985
                            break;
11986
                    }
11987
                }
11988
            }
11989
            $pdf = new PDF();
11990
            $result = $pdf->html_to_pdf(
11991
                $files_to_export,
11992
                $this->name,
11993
                $this->cc,
11994
                true
11995
            );
11996
11997
            return $result;
11998
        }
11999
12000
        return false;
12001
    }
12002
12003
    /**
12004
     * Temp function to be moved in main_api or the best place around for this.
12005
     * Creates a file path if it doesn't exist.
12006
     *
12007
     * @param string $path
12008
     */
12009
    public function create_path($path)
12010
    {
12011
        $path_bits = explode('/', dirname($path));
12012
12013
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12014
        $path_built = IS_WINDOWS_OS ? '' : '/';
12015
        foreach ($path_bits as $bit) {
12016
            if (!empty($bit)) {
12017
                $new_path = $path_built.$bit;
12018
                if (is_dir($new_path)) {
12019
                    $path_built = $new_path.'/';
12020
                } else {
12021
                    mkdir($new_path, api_get_permissions_for_new_directories());
12022
                    $path_built = $new_path.'/';
12023
                }
12024
            }
12025
        }
12026
    }
12027
12028
    /**
12029
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12030
     *
12031
     * @return bool The results of the unlink function, or false if there was no image to start with
12032
     */
12033
    public function delete_lp_image()
12034
    {
12035
        $img = $this->get_preview_image();
12036
        if ($img != '') {
12037
            $del_file = $this->get_preview_image_path(null, 'sys');
12038
            if (isset($del_file) && file_exists($del_file)) {
12039
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12040
                if (file_exists($del_file_2)) {
12041
                    unlink($del_file_2);
12042
                }
12043
                $this->set_preview_image('');
12044
12045
                return @unlink($del_file);
12046
            }
12047
        }
12048
12049
        return false;
12050
    }
12051
12052
    /**
12053
     * Uploads an author image to the upload/learning_path/images directory.
12054
     *
12055
     * @param array    The image array, coming from the $_FILES superglobal
12056
     *
12057
     * @return bool True on success, false on error
12058
     */
12059
    public function upload_image($image_array)
12060
    {
12061
        if (!empty($image_array['name'])) {
12062
            $upload_ok = process_uploaded_file($image_array);
12063
            $has_attachment = true;
12064
        }
12065
12066
        if ($upload_ok && $has_attachment) {
12067
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12068
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12069
            $updir = $sys_course_path.$courseDir;
12070
            // Try to add an extension to the file if it hasn't one.
12071
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12072
12073
            if (filter_extension($new_file_name)) {
12074
                $file_extension = explode('.', $image_array['name']);
12075
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12076
                $filename = uniqid('');
12077
                $new_file_name = $filename.'.'.$file_extension;
12078
                $new_path = $updir.'/'.$new_file_name;
12079
12080
                // Resize the image.
12081
                $temp = new Image($image_array['tmp_name']);
12082
                $temp->resize(104);
12083
                $result = $temp->send_image($new_path);
12084
12085
                // Storing the image filename.
12086
                if ($result) {
12087
                    $this->set_preview_image($new_file_name);
12088
12089
                    //Resize to 64px to use on course homepage
12090
                    $temp->resize(64);
12091
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12092
12093
                    return true;
12094
                }
12095
            }
12096
        }
12097
12098
        return false;
12099
    }
12100
12101
    /**
12102
     * @param int    $lp_id
12103
     * @param string $status
12104
     */
12105
    public function set_autolaunch($lp_id, $status)
12106
    {
12107
        $course_id = api_get_course_int_id();
12108
        $lp_id = intval($lp_id);
12109
        $status = intval($status);
12110
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12111
12112
        // Setting everything to autolaunch = 0
12113
        $attributes['autolaunch'] = 0;
12114
        $where = [
12115
            'session_id = ? AND c_id = ? ' => [
12116
                api_get_session_id(),
12117
                $course_id,
12118
            ],
12119
        ];
12120
        Database::update($lp_table, $attributes, $where);
12121
        if ($status == 1) {
12122
            //Setting my lp_id to autolaunch = 1
12123
            $attributes['autolaunch'] = 1;
12124
            $where = [
12125
                'iid = ? AND session_id = ? AND c_id = ?' => [
12126
                    $lp_id,
12127
                    api_get_session_id(),
12128
                    $course_id,
12129
                ],
12130
            ];
12131
            Database::update($lp_table, $attributes, $where);
12132
        }
12133
    }
12134
12135
    /**
12136
     * Gets previous_item_id for the next element of the lp_item table.
12137
     *
12138
     * @author Isaac flores paz
12139
     *
12140
     * @return int Previous item ID
12141
     */
12142
    public function select_previous_item_id()
12143
    {
12144
        $course_id = api_get_course_int_id();
12145
        if ($this->debug > 0) {
12146
            error_log('In learnpath::select_previous_item_id()', 0);
12147
        }
12148
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12149
12150
        // Get the max order of the items
12151
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12152
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12153
        $rs_max_order = Database::query($sql);
12154
        $row_max_order = Database::fetch_object($rs_max_order);
12155
        $max_order = $row_max_order->display_order;
12156
        // Get the previous item ID
12157
        $sql = "SELECT iid as previous FROM $table_lp_item
12158
                WHERE 
12159
                    c_id = $course_id AND 
12160
                    lp_id = ".$this->lp_id." AND 
12161
                    display_order = '$max_order' ";
12162
        $rs_max = Database::query($sql);
12163
        $row_max = Database::fetch_object($rs_max);
12164
12165
        // Return the previous item ID
12166
        return $row_max->previous;
12167
    }
12168
12169
    /**
12170
     * Copies an LP.
12171
     */
12172
    public function copy()
12173
    {
12174
        // Course builder
12175
        $cb = new CourseBuilder();
12176
12177
        //Setting tools that will be copied
12178
        $cb->set_tools_to_build(['learnpaths']);
12179
12180
        //Setting elements that will be copied
12181
        $cb->set_tools_specific_id_list(
12182
            ['learnpaths' => [$this->lp_id]]
12183
        );
12184
12185
        $course = $cb->build();
12186
12187
        //Course restorer
12188
        $course_restorer = new CourseRestorer($course);
12189
        $course_restorer->set_add_text_in_items(true);
12190
        $course_restorer->set_tool_copy_settings(
12191
            ['learnpaths' => ['reset_dates' => true]]
12192
        );
12193
        $course_restorer->restore(
12194
            api_get_course_id(),
12195
            api_get_session_id(),
12196
            false,
12197
            false
12198
        );
12199
    }
12200
12201
    /**
12202
     * Verify document size.
12203
     *
12204
     * @param string $s
12205
     *
12206
     * @return bool
12207
     */
12208
    public static function verify_document_size($s)
12209
    {
12210
        $post_max = ini_get('post_max_size');
12211
        if (substr($post_max, -1, 1) == 'M') {
12212
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12213
        } elseif (substr($post_max, -1, 1) == 'G') {
12214
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12215
        }
12216
        $upl_max = ini_get('upload_max_filesize');
12217
        if (substr($upl_max, -1, 1) == 'M') {
12218
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12219
        } elseif (substr($upl_max, -1, 1) == 'G') {
12220
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12221
        }
12222
        $documents_total_space = DocumentManager::documents_total_space();
12223
        $course_max_space = DocumentManager::get_course_quota();
12224
        $total_size = filesize($s) + $documents_total_space;
12225
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12226
            return true;
12227
        } else {
12228
            return false;
12229
        }
12230
    }
12231
12232
    /**
12233
     * Clear LP prerequisites.
12234
     */
12235
    public function clear_prerequisites()
12236
    {
12237
        $course_id = $this->get_course_int_id();
12238
        if ($this->debug > 0) {
12239
            error_log('In learnpath::clear_prerequisites()', 0);
12240
        }
12241
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12242
        $lp_id = $this->get_id();
12243
        //Cleaning prerequisites
12244
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12245
                WHERE c_id = $course_id AND lp_id = $lp_id";
12246
        Database::query($sql);
12247
12248
        //Cleaning mastery score for exercises
12249
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12250
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12251
        Database::query($sql);
12252
    }
12253
12254
    public function set_previous_step_as_prerequisite_for_all_items()
12255
    {
12256
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12257
        $course_id = $this->get_course_int_id();
12258
        $lp_id = $this->get_id();
12259
12260
        if (!empty($this->items)) {
12261
            $previous_item_id = null;
12262
            $previous_item_max = 0;
12263
            $previous_item_type = null;
12264
            $last_item_not_dir = null;
12265
            $last_item_not_dir_type = null;
12266
            $last_item_not_dir_max = null;
12267
12268
            foreach ($this->ordered_items as $itemId) {
12269
                $item = $this->getItem($itemId);
12270
                // if there was a previous item... (otherwise jump to set it)
12271
                if (!empty($previous_item_id)) {
12272
                    $current_item_id = $item->get_id(); //save current id
12273
                    if ($item->get_type() != 'dir') {
12274
                        // Current item is not a folder, so it qualifies to get a prerequisites
12275
                        if ($last_item_not_dir_type == 'quiz') {
12276
                            // if previous is quiz, mark its max score as default score to be achieved
12277
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12278
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12279
                            Database::query($sql);
12280
                        }
12281
                        // now simply update the prerequisite to set it to the last non-chapter item
12282
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12283
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12284
                        Database::query($sql);
12285
                        // record item as 'non-chapter' reference
12286
                        $last_item_not_dir = $item->get_id();
12287
                        $last_item_not_dir_type = $item->get_type();
12288
                        $last_item_not_dir_max = $item->get_max();
12289
                    }
12290
                } else {
12291
                    if ($item->get_type() != 'dir') {
12292
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12293
                        $last_item_not_dir = $item->get_id();
12294
                        $last_item_not_dir_type = $item->get_type();
12295
                        $last_item_not_dir_max = $item->get_max();
12296
                    }
12297
                }
12298
                // Saving the item as "previous item" for the next loop
12299
                $previous_item_id = $item->get_id();
12300
                $previous_item_max = $item->get_max();
12301
                $previous_item_type = $item->get_type();
12302
            }
12303
        }
12304
    }
12305
12306
    /**
12307
     * @param array $params
12308
     *
12309
     * @throws \Doctrine\ORM\OptimisticLockException
12310
     *
12311
     * @return int
12312
     */
12313
    public static function createCategory($params)
12314
    {
12315
        $em = Database::getManager();
12316
        $item = new CLpCategory();
12317
        $item->setName($params['name']);
12318
        $item->setCId($params['c_id']);
12319
        $em->persist($item);
12320
        $em->flush();
12321
12322
        api_item_property_update(
12323
            api_get_course_info(),
12324
            TOOL_LEARNPATH_CATEGORY,
12325
            $item->getId(),
12326
            'visible',
12327
            api_get_user_id()
12328
        );
12329
12330
        return $item->getId();
12331
    }
12332
12333
    /**
12334
     * @param array $params
12335
     *
12336
     * @throws \Doctrine\ORM\ORMException
12337
     * @throws \Doctrine\ORM\OptimisticLockException
12338
     * @throws \Doctrine\ORM\TransactionRequiredException
12339
     */
12340
    public static function updateCategory($params)
12341
    {
12342
        $em = Database::getManager();
12343
        /** @var CLpCategory $item */
12344
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12345
        if ($item) {
12346
            $item->setName($params['name']);
12347
            $em->merge($item);
12348
            $em->flush();
12349
        }
12350
    }
12351
12352
    /**
12353
     * @param int $id
12354
     *
12355
     * @throws \Doctrine\ORM\ORMException
12356
     * @throws \Doctrine\ORM\OptimisticLockException
12357
     * @throws \Doctrine\ORM\TransactionRequiredException
12358
     */
12359
    public static function moveUpCategory($id)
12360
    {
12361
        $id = (int) $id;
12362
        $em = Database::getManager();
12363
        /** @var CLpCategory $item */
12364
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12365
        if ($item) {
12366
            $position = $item->getPosition() - 1;
12367
            $item->setPosition($position);
12368
            $em->persist($item);
12369
            $em->flush();
12370
        }
12371
    }
12372
12373
    /**
12374
     * @param int $id
12375
     *
12376
     * @throws \Doctrine\ORM\ORMException
12377
     * @throws \Doctrine\ORM\OptimisticLockException
12378
     * @throws \Doctrine\ORM\TransactionRequiredException
12379
     */
12380
    public static function moveDownCategory($id)
12381
    {
12382
        $id = (int) $id;
12383
        $em = Database::getManager();
12384
        /** @var CLpCategory $item */
12385
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12386
        if ($item) {
12387
            $position = $item->getPosition() + 1;
12388
            $item->setPosition($position);
12389
            $em->persist($item);
12390
            $em->flush();
12391
        }
12392
    }
12393
12394
    /**
12395
     * @param int $courseId
12396
     *
12397
     * @throws \Doctrine\ORM\Query\QueryException
12398
     *
12399
     * @return int|mixed
12400
     */
12401
    public static function getCountCategories($courseId)
12402
    {
12403
        if (empty($courseId)) {
12404
            return 0;
12405
        }
12406
        $em = Database::getManager();
12407
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12408
        $query->setParameter('id', $courseId);
12409
12410
        return $query->getSingleScalarResult();
12411
    }
12412
12413
    /**
12414
     * @param int $courseId
12415
     *
12416
     * @return mixed
12417
     */
12418
    public static function getCategories($courseId)
12419
    {
12420
        $em = Database::getManager();
12421
        //Default behaviour
12422
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12423
            array('cId' => $course_id),
12424
            array('name' => 'ASC')
12425
        );*/
12426
12427
        // Using doctrine extensions
12428
        /** @var SortableRepository $repo */
12429
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12430
        $items = $repo
12431
            ->getBySortableGroupsQuery(['cId' => $courseId])
12432
            ->getResult();
12433
12434
        return $items;
12435
    }
12436
12437
    /**
12438
     * @param int $id
12439
     *
12440
     * @throws \Doctrine\ORM\ORMException
12441
     * @throws \Doctrine\ORM\OptimisticLockException
12442
     * @throws \Doctrine\ORM\TransactionRequiredException
12443
     *
12444
     * @return CLpCategory
12445
     */
12446
    public static function getCategory($id)
12447
    {
12448
        $id = (int) $id;
12449
        $em = Database::getManager();
12450
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12451
12452
        return $item;
12453
    }
12454
12455
    /**
12456
     * @param int $courseId
12457
     *
12458
     * @return array
12459
     */
12460
    public static function getCategoryByCourse($courseId)
12461
    {
12462
        $em = Database::getManager();
12463
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12464
            ['cId' => $courseId]
12465
        );
12466
12467
        return $items;
12468
    }
12469
12470
    /**
12471
     * @param int $id
12472
     *
12473
     * @throws \Doctrine\ORM\ORMException
12474
     * @throws \Doctrine\ORM\OptimisticLockException
12475
     * @throws \Doctrine\ORM\TransactionRequiredException
12476
     *
12477
     * @return mixed
12478
     */
12479
    public static function deleteCategory($id)
12480
    {
12481
        $em = Database::getManager();
12482
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12483
        if ($item) {
12484
            $courseId = $item->getCId();
12485
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12486
            $query->setParameter('id', $courseId);
12487
            $query->setParameter('catId', $item->getId());
12488
            $lps = $query->getResult();
12489
12490
            // Setting category = 0.
12491
            if ($lps) {
12492
                foreach ($lps as $lpItem) {
12493
                    $lpItem->setCategoryId(0);
12494
                }
12495
            }
12496
12497
            // Removing category.
12498
            $em->remove($item);
12499
            $em->flush();
12500
12501
            $courseInfo = api_get_course_info_by_id($courseId);
12502
            $sessionId = api_get_session_id();
12503
12504
            // Delete link tool
12505
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12506
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12507
            // Delete tools
12508
            $sql = "DELETE FROM $tbl_tool
12509
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12510
            Database::query($sql);
12511
        }
12512
    }
12513
12514
    /**
12515
     * @param int  $courseId
12516
     * @param bool $addSelectOption
12517
     *
12518
     * @return mixed
12519
     */
12520
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12521
    {
12522
        $items = self::getCategoryByCourse($courseId);
12523
        $cats = [];
12524
        if ($addSelectOption) {
12525
            $cats = [get_lang('SelectACategory')];
12526
        }
12527
12528
        if (!empty($items)) {
12529
            foreach ($items as $cat) {
12530
                $cats[$cat->getId()] = $cat->getName();
12531
            }
12532
        }
12533
12534
        return $cats;
12535
    }
12536
12537
    /**
12538
     * @param string $courseCode
12539
     * @param int    $lpId
12540
     * @param int    $user_id
12541
     *
12542
     * @return learnpath
12543
     */
12544
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12545
    {
12546
        $debug = 0;
12547
        $learnPath = null;
12548
        $lpObject = Session::read('lpobject');
12549
        if ($lpObject !== null) {
12550
            $learnPath = unserialize($lpObject);
12551
            if ($debug) {
12552
                error_log('getLpFromSession: unserialize');
12553
                error_log('------getLpFromSession------');
12554
                error_log('------unserialize------');
12555
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12556
                error_log("api_get_sessionid: ".api_get_session_id());
12557
            }
12558
        }
12559
12560
        if (!is_object($learnPath)) {
12561
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12562
            if ($debug) {
12563
                error_log('------getLpFromSession------');
12564
                error_log('getLpFromSession: create new learnpath');
12565
                error_log("create new LP with $courseCode - $lpId - $user_id");
12566
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12567
                error_log("api_get_sessionid: ".api_get_session_id());
12568
            }
12569
        }
12570
12571
        return $learnPath;
12572
    }
12573
12574
    /**
12575
     * @param int $itemId
12576
     *
12577
     * @return learnpathItem|false
12578
     */
12579
    public function getItem($itemId)
12580
    {
12581
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12582
            return $this->items[$itemId];
12583
        }
12584
12585
        return false;
12586
    }
12587
12588
    /**
12589
     * @return int
12590
     */
12591
    public function getCategoryId()
12592
    {
12593
        return (int) $this->categoryId;
12594
    }
12595
12596
    /**
12597
     * @param int $categoryId
12598
     *
12599
     * @return bool
12600
     */
12601
    public function setCategoryId($categoryId)
12602
    {
12603
        $this->categoryId = (int) $categoryId;
12604
        $table = Database::get_course_table(TABLE_LP_MAIN);
12605
        $lp_id = $this->get_id();
12606
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12607
                WHERE iid = $lp_id";
12608
        Database::query($sql);
12609
12610
        return true;
12611
    }
12612
12613
    /**
12614
     * Get whether this is a learning path with the possibility to subscribe
12615
     * users or not.
12616
     *
12617
     * @return int
12618
     */
12619
    public function getSubscribeUsers()
12620
    {
12621
        return $this->subscribeUsers;
12622
    }
12623
12624
    /**
12625
     * Set whether this is a learning path with the possibility to subscribe
12626
     * users or not.
12627
     *
12628
     * @param int $value (0 = false, 1 = true)
12629
     *
12630
     * @return bool
12631
     */
12632
    public function setSubscribeUsers($value)
12633
    {
12634
        if ($this->debug > 0) {
12635
            error_log('In learnpath::set_subscribe_users()', 0);
12636
        }
12637
        $this->subscribeUsers = (int) $value;
12638
        $table = Database::get_course_table(TABLE_LP_MAIN);
12639
        $lp_id = $this->get_id();
12640
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12641
                WHERE iid = $lp_id";
12642
        Database::query($sql);
12643
12644
        return true;
12645
    }
12646
12647
    /**
12648
     * Calculate the count of stars for a user in this LP
12649
     * This calculation is based on the following rules:
12650
     * - the student gets one star when he gets to 50% of the learning path
12651
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12652
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12653
     * - the student gets the final star when the score for the *last* test is >= 80%.
12654
     *
12655
     * @param int $sessionId Optional. The session ID
12656
     *
12657
     * @return int The count of stars
12658
     */
12659
    public function getCalculateStars($sessionId = 0)
12660
    {
12661
        $stars = 0;
12662
        $progress = self::getProgress(
12663
            $this->lp_id,
12664
            $this->user_id,
12665
            $this->course_int_id,
12666
            $sessionId
12667
        );
12668
12669
        if ($progress >= 50) {
12670
            $stars++;
12671
        }
12672
12673
        // Calculate stars chapters evaluation
12674
        $exercisesItems = $this->getExercisesItems();
12675
12676
        if (!empty($exercisesItems)) {
12677
            $totalResult = 0;
12678
12679
            foreach ($exercisesItems as $exerciseItem) {
12680
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12681
                    $this->user_id,
12682
                    $exerciseItem->path,
12683
                    $this->course_int_id,
12684
                    $sessionId,
12685
                    $this->lp_id,
12686
                    $exerciseItem->db_id
12687
                );
12688
12689
                $exerciseResultInfo = end($exerciseResultInfo);
12690
12691
                if (!$exerciseResultInfo) {
12692
                    continue;
12693
                }
12694
12695
                if (!empty($exerciseResultInfo['max_score'])) {
12696
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12697
                } else {
12698
                    $exerciseResult = 0;
12699
                }
12700
                $totalResult += $exerciseResult;
12701
            }
12702
12703
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12704
12705
            if ($totalExerciseAverage >= 50) {
12706
                $stars++;
12707
            }
12708
12709
            if ($totalExerciseAverage >= 80) {
12710
                $stars++;
12711
            }
12712
        }
12713
12714
        // Calculate star for final evaluation
12715
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12716
12717
        if (!empty($finalEvaluationItem)) {
12718
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12719
                $this->user_id,
12720
                $finalEvaluationItem->path,
12721
                $this->course_int_id,
12722
                $sessionId,
12723
                $this->lp_id,
12724
                $finalEvaluationItem->db_id
12725
            );
12726
12727
            $evaluationResultInfo = end($evaluationResultInfo);
12728
12729
            if ($evaluationResultInfo) {
12730
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12731
12732
                if ($evaluationResult >= 80) {
12733
                    $stars++;
12734
                }
12735
            }
12736
        }
12737
12738
        return $stars;
12739
    }
12740
12741
    /**
12742
     * Get the items of exercise type.
12743
     *
12744
     * @return array The items. Otherwise return false
12745
     */
12746
    public function getExercisesItems()
12747
    {
12748
        $exercises = [];
12749
        foreach ($this->items as $item) {
12750
            if ($item->type != 'quiz') {
12751
                continue;
12752
            }
12753
            $exercises[] = $item;
12754
        }
12755
12756
        array_pop($exercises);
12757
12758
        return $exercises;
12759
    }
12760
12761
    /**
12762
     * Get the item of exercise type (evaluation type).
12763
     *
12764
     * @return array The final evaluation. Otherwise return false
12765
     */
12766
    public function getFinalEvaluationItem()
12767
    {
12768
        $exercises = [];
12769
        foreach ($this->items as $item) {
12770
            if ($item->type != 'quiz') {
12771
                continue;
12772
            }
12773
12774
            $exercises[] = $item;
12775
        }
12776
12777
        return array_pop($exercises);
12778
    }
12779
12780
    /**
12781
     * Calculate the total points achieved for the current user in this learning path.
12782
     *
12783
     * @param int $sessionId Optional. The session Id
12784
     *
12785
     * @return int
12786
     */
12787
    public function getCalculateScore($sessionId = 0)
12788
    {
12789
        // Calculate stars chapters evaluation
12790
        $exercisesItems = $this->getExercisesItems();
12791
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12792
        $totalExercisesResult = 0;
12793
        $totalEvaluationResult = 0;
12794
12795
        if ($exercisesItems !== false) {
12796
            foreach ($exercisesItems as $exerciseItem) {
12797
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12798
                    $this->user_id,
12799
                    $exerciseItem->path,
12800
                    $this->course_int_id,
12801
                    $sessionId,
12802
                    $this->lp_id,
12803
                    $exerciseItem->db_id
12804
                );
12805
12806
                $exerciseResultInfo = end($exerciseResultInfo);
12807
12808
                if (!$exerciseResultInfo) {
12809
                    continue;
12810
                }
12811
12812
                $totalExercisesResult += $exerciseResultInfo['score'];
12813
            }
12814
        }
12815
12816
        if (!empty($finalEvaluationItem)) {
12817
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12818
                $this->user_id,
12819
                $finalEvaluationItem->path,
12820
                $this->course_int_id,
12821
                $sessionId,
12822
                $this->lp_id,
12823
                $finalEvaluationItem->db_id
12824
            );
12825
12826
            $evaluationResultInfo = end($evaluationResultInfo);
12827
12828
            if ($evaluationResultInfo) {
12829
                $totalEvaluationResult += $evaluationResultInfo['score'];
12830
            }
12831
        }
12832
12833
        return $totalExercisesResult + $totalEvaluationResult;
12834
    }
12835
12836
    /**
12837
     * Check if URL is not allowed to be show in a iframe.
12838
     *
12839
     * @param string $src
12840
     *
12841
     * @return string
12842
     */
12843
    public function fixBlockedLinks($src)
12844
    {
12845
        $urlInfo = parse_url($src);
12846
12847
        $platformProtocol = 'https';
12848
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12849
            $platformProtocol = 'http';
12850
        }
12851
12852
        $protocolFixApplied = false;
12853
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12854
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12855
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12856
12857
        if ($platformProtocol != $scheme) {
12858
            Session::write('x_frame_source', $src);
12859
            $src = 'blank.php?error=x_frames_options';
12860
            $protocolFixApplied = true;
12861
        }
12862
12863
        if ($protocolFixApplied == false) {
12864
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12865
                // Check X-Frame-Options
12866
                $ch = curl_init();
12867
                $options = [
12868
                    CURLOPT_URL => $src,
12869
                    CURLOPT_RETURNTRANSFER => true,
12870
                    CURLOPT_HEADER => true,
12871
                    CURLOPT_FOLLOWLOCATION => true,
12872
                    CURLOPT_ENCODING => "",
12873
                    CURLOPT_AUTOREFERER => true,
12874
                    CURLOPT_CONNECTTIMEOUT => 120,
12875
                    CURLOPT_TIMEOUT => 120,
12876
                    CURLOPT_MAXREDIRS => 10,
12877
                ];
12878
12879
                $proxySettings = api_get_configuration_value('proxy_settings');
12880
                if (!empty($proxySettings) &&
12881
                    isset($proxySettings['curl_setopt_array'])
12882
                ) {
12883
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12884
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12885
                }
12886
12887
                curl_setopt_array($ch, $options);
12888
                $response = curl_exec($ch);
12889
                $httpCode = curl_getinfo($ch);
12890
                $headers = substr($response, 0, $httpCode['header_size']);
12891
12892
                $error = false;
12893
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12894
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12895
                ) {
12896
                    $error = true;
12897
                }
12898
12899
                if ($error) {
12900
                    Session::write('x_frame_source', $src);
12901
                    $src = 'blank.php?error=x_frames_options';
12902
                }
12903
            }
12904
        }
12905
12906
        return $src;
12907
    }
12908
12909
    /**
12910
     * Check if this LP has a created forum in the basis course.
12911
     *
12912
     * @return bool
12913
     */
12914
    public function lpHasForum()
12915
    {
12916
        $forumTable = Database::get_course_table(TABLE_FORUM);
12917
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12918
12919
        $fakeFrom = "
12920
            $forumTable f
12921
            INNER JOIN $itemProperty ip
12922
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12923
        ";
12924
12925
        $resultData = Database::select(
12926
            'COUNT(f.iid) AS qty',
12927
            $fakeFrom,
12928
            [
12929
                'where' => [
12930
                    'ip.visibility != ? AND ' => 2,
12931
                    'ip.tool = ? AND ' => TOOL_FORUM,
12932
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12933
                    'f.lp_id = ?' => intval($this->lp_id),
12934
                ],
12935
            ],
12936
            'first'
12937
        );
12938
12939
        return $resultData['qty'] > 0;
12940
    }
12941
12942
    /**
12943
     * Get the forum for this learning path.
12944
     *
12945
     * @param int $sessionId
12946
     *
12947
     * @return bool
12948
     */
12949
    public function getForum($sessionId = 0)
12950
    {
12951
        $forumTable = Database::get_course_table(TABLE_FORUM);
12952
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12953
12954
        $fakeFrom = "$forumTable f
12955
            INNER JOIN $itemProperty ip ";
12956
12957
        if ($this->lp_session_id == 0) {
12958
            $fakeFrom .= "
12959
                ON (
12960
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12961
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12962
                    )
12963
                )
12964
            ";
12965
        } else {
12966
            $fakeFrom .= "
12967
                ON (
12968
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12969
                )
12970
            ";
12971
        }
12972
12973
        $resultData = Database::select(
12974
            'f.*',
12975
            $fakeFrom,
12976
            [
12977
                'where' => [
12978
                    'ip.visibility != ? AND ' => 2,
12979
                    'ip.tool = ? AND ' => TOOL_FORUM,
12980
                    'f.session_id = ? AND ' => $sessionId,
12981
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12982
                    'f.lp_id = ?' => intval($this->lp_id),
12983
                ],
12984
            ],
12985
            'first'
12986
        );
12987
12988
        if (empty($resultData)) {
12989
            return false;
12990
        }
12991
12992
        return $resultData;
12993
    }
12994
12995
    /**
12996
     * Create a forum for this learning path.
12997
     *
12998
     * @param int $forumCategoryId
12999
     *
13000
     * @return int The forum ID if was created. Otherwise return false
13001
     */
13002
    public function createForum($forumCategoryId)
13003
    {
13004
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13005
13006
        $forumId = store_forum(
13007
            [
13008
                'lp_id' => $this->lp_id,
13009
                'forum_title' => $this->name,
13010
                'forum_comment' => null,
13011
                'forum_category' => intval($forumCategoryId),
13012
                'students_can_edit_group' => ['students_can_edit' => 0],
13013
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13014
                'default_view_type_group' => ['default_view_type' => 'flat'],
13015
                'group_forum' => 0,
13016
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13017
            ],
13018
            [],
13019
            true
13020
        );
13021
13022
        return $forumId;
13023
    }
13024
13025
    /**
13026
     * Get the LP Final Item form.
13027
     *
13028
     * @throws Exception
13029
     * @throws HTML_QuickForm_Error
13030
     *
13031
     * @return string
13032
     */
13033
    public function getFinalItemForm()
13034
    {
13035
        $finalItem = $this->getFinalItem();
13036
        $title = '';
13037
13038
        if ($finalItem) {
13039
            $title = $finalItem->get_title();
13040
            $buttonText = get_lang('Save');
13041
            $content = $this->getSavedFinalItem();
13042
        } else {
13043
            $buttonText = get_lang('LPCreateDocument');
13044
            $content = $this->getFinalItemTemplate();
13045
        }
13046
13047
        $courseInfo = api_get_course_info();
13048
        $result = $this->generate_lp_folder($courseInfo);
13049
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13050
        $relative_prefix = '../../';
13051
13052
        $editorConfig = [
13053
            'ToolbarSet' => 'LearningPathDocuments',
13054
            'Width' => '100%',
13055
            'Height' => '500',
13056
            'FullPage' => true,
13057
            'CreateDocumentDir' => $relative_prefix,
13058
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13059
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13060
        ];
13061
13062
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13063
            'type' => 'document',
13064
            'lp_id' => $this->lp_id,
13065
        ]);
13066
13067
        $form = new FormValidator('final_item', 'POST', $url);
13068
        $form->addText('title', get_lang('Title'));
13069
        $form->addButtonSave($buttonText);
13070
        $form->addHtml(
13071
            Display::return_message(
13072
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13073
                'normal',
13074
                false
13075
            )
13076
        );
13077
13078
        $renderer = $form->defaultRenderer();
13079
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13080
13081
        $form->addHtmlEditor(
13082
            'content_lp_certificate',
13083
            null,
13084
            true,
13085
            false,
13086
            $editorConfig,
13087
            true
13088
        );
13089
        $form->addHidden('action', 'add_final_item');
13090
        $form->addHidden('path', Session::read('pathItem'));
13091
        $form->addHidden('previous', $this->get_last());
13092
        $form->setDefaults(
13093
            ['title' => $title, 'content_lp_certificate' => $content]
13094
        );
13095
13096
        if ($form->validate()) {
13097
            $values = $form->exportValues();
13098
            $lastItemId = $this->get_last();
13099
13100
            if (!$finalItem) {
13101
                $documentId = $this->create_document(
13102
                    $this->course_info,
13103
                    $values['content_lp_certificate'],
13104
                    $values['title']
13105
                );
13106
                $this->add_item(
13107
                    0,
13108
                    $lastItemId,
13109
                    'final_item',
13110
                    $documentId,
13111
                    $values['title'],
13112
                    ''
13113
                );
13114
13115
                Display::addFlash(
13116
                    Display::return_message(get_lang('Added'))
13117
                );
13118
            } else {
13119
                $this->edit_document($this->course_info);
13120
            }
13121
        }
13122
13123
        return $form->returnForm();
13124
    }
13125
13126
    /**
13127
     * Check if the current lp item is first, both, last or none from lp list.
13128
     *
13129
     * @param int $currentItemId
13130
     *
13131
     * @return string
13132
     */
13133
    public function isFirstOrLastItem($currentItemId)
13134
    {
13135
        if ($this->debug > 0) {
13136
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
13137
        }
13138
13139
        $lpItemId = [];
13140
        $typeListNotToVerify = self::getChapterTypes();
13141
13142
        // Using get_toc() function instead $this->items because returns the correct order of the items
13143
        foreach ($this->get_toc() as $item) {
13144
            if (!in_array($item['type'], $typeListNotToVerify)) {
13145
                $lpItemId[] = $item['id'];
13146
            }
13147
        }
13148
13149
        $lastLpItemIndex = count($lpItemId) - 1;
13150
        $position = array_search($currentItemId, $lpItemId);
13151
13152
        switch ($position) {
13153
            case 0:
13154
                if (!$lastLpItemIndex) {
13155
                    $answer = 'both';
13156
                    break;
13157
                }
13158
13159
                $answer = 'first';
13160
                break;
13161
            case $lastLpItemIndex:
13162
                $answer = 'last';
13163
                break;
13164
            default:
13165
                $answer = 'none';
13166
        }
13167
13168
        return $answer;
13169
    }
13170
13171
    /**
13172
     * Get whether this is a learning path with the accumulated SCORM time or not.
13173
     *
13174
     * @return int
13175
     */
13176
    public function getAccumulateScormTime()
13177
    {
13178
        return $this->accumulateScormTime;
13179
    }
13180
13181
    /**
13182
     * Set whether this is a learning path with the accumulated SCORM time or not.
13183
     *
13184
     * @param int $value (0 = false, 1 = true)
13185
     *
13186
     * @return bool Always returns true
13187
     */
13188
    public function setAccumulateScormTime($value)
13189
    {
13190
        if ($this->debug > 0) {
13191
            error_log('In learnpath::setAccumulateScormTime()', 0);
13192
        }
13193
        $this->accumulateScormTime = intval($value);
13194
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13195
        $lp_id = $this->get_id();
13196
        $sql = "UPDATE $lp_table 
13197
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13198
                WHERE iid = $lp_id";
13199
        Database::query($sql);
13200
13201
        return true;
13202
    }
13203
13204
    /**
13205
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13206
     * the new learning path tool.
13207
     *
13208
     * The function is a big switch on tool type.
13209
     * In each case, we query the corresponding table for information and build the link
13210
     * with that information.
13211
     *
13212
     * @author Yannick Warnier <[email protected]> - rebranding based on
13213
     * previous work (display_addedresource_link_in_learnpath())
13214
     *
13215
     * @param int $course_id      Course code
13216
     * @param int $learningPathId The learning path ID (in lp table)
13217
     * @param int $id_in_path     the unique index in the items table
13218
     * @param int $lpViewId
13219
     *
13220
     * @return string
13221
     */
13222
    public static function rl_get_resource_link_for_learnpath(
13223
        $course_id,
13224
        $learningPathId,
13225
        $id_in_path,
13226
        $lpViewId
13227
    ) {
13228
        $session_id = api_get_session_id();
13229
        $course_info = api_get_course_info_by_id($course_id);
13230
13231
        $learningPathId = (int) $learningPathId;
13232
        $id_in_path = (int) $id_in_path;
13233
        $lpViewId = (int) $lpViewId;
13234
13235
        $em = Database::getManager();
13236
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13237
13238
        /** @var CLpItem $rowItem */
13239
        $rowItem = $lpItemRepo->findOneBy([
13240
            'cId' => $course_id,
13241
            'lpId' => $learningPathId,
13242
            'iid' => $id_in_path,
13243
        ]);
13244
13245
        if (!$rowItem) {
13246
            // Try one more time with "id"
13247
            /** @var CLpItem $rowItem */
13248
            $rowItem = $lpItemRepo->findOneBy([
13249
                'cId' => $course_id,
13250
                'lpId' => $learningPathId,
13251
                'id' => $id_in_path,
13252
            ]);
13253
13254
            if (!$rowItem) {
13255
                return -1;
13256
            }
13257
        }
13258
13259
        $course_code = $course_info['code'];
13260
        $type = $rowItem->getItemType();
13261
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13262
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13263
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13264
        $link = '';
13265
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13266
13267
        switch ($type) {
13268
            case 'dir':
13269
                return $main_dir_path.'lp/blank.php';
13270
            case TOOL_CALENDAR_EVENT:
13271
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13272
            case TOOL_ANNOUNCEMENT:
13273
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13274
            case TOOL_LINK:
13275
                $linkInfo = Link::getLinkInfo($id);
13276
                if (isset($linkInfo['url'])) {
13277
                    return $linkInfo['url'];
13278
                }
13279
13280
                return '';
13281
            case TOOL_QUIZ:
13282
                if (empty($id)) {
13283
                    return '';
13284
                }
13285
13286
                // Get the lp_item_view with the highest view_count.
13287
                $learnpathItemViewResult = $em
13288
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13289
                    ->findBy(
13290
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13291
                        ['viewCount' => 'DESC'],
13292
                        1
13293
                    );
13294
                /** @var CLpItemView $learnpathItemViewData */
13295
                $learnpathItemViewData = current($learnpathItemViewResult);
13296
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13297
13298
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13299
                    .http_build_query([
13300
                        'lp_init' => 1,
13301
                        'learnpath_item_view_id' => $learnpathItemViewId,
13302
                        'learnpath_id' => $learningPathId,
13303
                        'learnpath_item_id' => $id_in_path,
13304
                        'exerciseId' => $id,
13305
                    ]);
13306
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13307
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13308
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13309
                $myrow = Database::fetch_array($result);
13310
                $path = $myrow['path'];
13311
13312
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13313
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13314
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13315
            case TOOL_FORUM:
13316
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13317
            case TOOL_THREAD:
13318
                // forum post
13319
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13320
                if (empty($id)) {
13321
                    return '';
13322
                }
13323
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13324
                $result = Database::query($sql);
13325
                $myrow = Database::fetch_array($result);
13326
13327
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13328
                    .$extraParams;
13329
            case TOOL_POST:
13330
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13331
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13332
                $myrow = Database::fetch_array($result);
13333
13334
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13335
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13336
            case TOOL_READOUT_TEXT:
13337
                return api_get_path(WEB_CODE_PATH).
13338
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13339
            case TOOL_DOCUMENT:
13340
                $document = $em
13341
                    ->getRepository('ChamiloCourseBundle:CDocument')
13342
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
13343
13344
                if (empty($document)) {
13345
                    // Try with normal id
13346
                    $document = $em
13347
                        ->getRepository('ChamiloCourseBundle:CDocument')
13348
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
13349
13350
                    if (empty($document)) {
13351
                        return '';
13352
                    }
13353
                }
13354
13355
                $documentPathInfo = pathinfo($document->getPath());
13356
                $jplayerSupportedFiles = ['mp4', 'ogv', 'flv', 'm4v'];
13357
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13358
                $showDirectUrl = !in_array($extension, $jplayerSupportedFiles);
13359
13360
                $openmethod = 2;
13361
                $officedoc = false;
13362
                Session::write('openmethod', $openmethod);
13363
                Session::write('officedoc', $officedoc);
13364
13365
                if ($showDirectUrl) {
13366
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13367
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13368
                        if (Link::isPdfLink($file)) {
13369
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13370
13371
                            return $pdfUrl;
13372
                        }
13373
                    }
13374
13375
                    return $file;
13376
                }
13377
13378
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13379
            case TOOL_LP_FINAL_ITEM:
13380
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13381
                    .$extraParams;
13382
            case 'assignments':
13383
                return $main_dir_path.'work/work.php?'.$extraParams;
13384
            case TOOL_DROPBOX:
13385
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13386
            case 'introduction_text': //DEPRECATED
13387
                return '';
13388
            case TOOL_COURSE_DESCRIPTION:
13389
                return $main_dir_path.'course_description?'.$extraParams;
13390
            case TOOL_GROUP:
13391
                return $main_dir_path.'group/group.php?'.$extraParams;
13392
            case TOOL_USER:
13393
                return $main_dir_path.'user/user.php?'.$extraParams;
13394
            case TOOL_STUDENTPUBLICATION:
13395
                if (!empty($rowItem->getPath())) {
13396
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13397
                }
13398
13399
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13400
        } //end switch
13401
13402
        return $link;
13403
    }
13404
13405
    /**
13406
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13407
     *
13408
     * @author Yannick Warnier <[email protected]>
13409
     *
13410
     * @param string $course_code    Course code
13411
     * @param int    $learningPathId
13412
     * @param int    $id_in_path     The resource ID
13413
     *
13414
     * @return string
13415
     */
13416
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13417
    {
13418
        $_course = api_get_course_info($course_code);
13419
        $course_id = $_course['real_id'];
13420
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13421
        $learningPathId = (int) $learningPathId;
13422
        $id_in_path = (int) $id_in_path;
13423
13424
        $sql = "SELECT item_type, title, ref 
13425
                FROM $tbl_lp_item
13426
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13427
        $res_item = Database::query($sql);
13428
13429
        if (Database::num_rows($res_item) < 1) {
13430
            return '';
13431
        }
13432
        $row_item = Database::fetch_array($res_item);
13433
        $type = strtolower($row_item['item_type']);
13434
        $id = $row_item['ref'];
13435
        $output = '';
13436
13437
        switch ($type) {
13438
            case TOOL_CALENDAR_EVENT:
13439
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13440
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13441
                $myrow = Database::fetch_array($result);
13442
                $output = $myrow['title'];
13443
                break;
13444
            case TOOL_ANNOUNCEMENT:
13445
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13446
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13447
                $myrow = Database::fetch_array($result);
13448
                $output = $myrow['title'];
13449
                break;
13450
            case TOOL_LINK:
13451
                // Doesn't take $target into account.
13452
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13453
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13454
                $myrow = Database::fetch_array($result);
13455
                $output = $myrow['title'];
13456
                break;
13457
            case TOOL_QUIZ:
13458
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13459
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13460
                $myrow = Database::fetch_array($result);
13461
                $output = $myrow['title'];
13462
                break;
13463
            case TOOL_FORUM:
13464
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13465
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13466
                $myrow = Database::fetch_array($result);
13467
                $output = $myrow['forum_name'];
13468
                break;
13469
            case TOOL_THREAD:  //=topics
13470
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13471
                // Grabbing the title of the post.
13472
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13473
                $result_title = Database::query($sql_title);
13474
                $myrow_title = Database::fetch_array($result_title);
13475
                $output = $myrow_title['post_title'];
13476
                break;
13477
            case TOOL_POST:
13478
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13479
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13480
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13481
                $result = Database::query($sql);
13482
                $post = Database::fetch_array($result);
13483
                $output = $post['post_title'];
13484
                break;
13485
            case 'dir':
13486
                $title = $row_item['title'];
13487
                if (!empty($title)) {
13488
                    $output = $title;
13489
                } else {
13490
                    $output = '-';
13491
                }
13492
                break;
13493
            case TOOL_DOCUMENT:
13494
                $title = $row_item['title'];
13495
                if (!empty($title)) {
13496
                    $output = $title;
13497
                } else {
13498
                    $output = '-';
13499
                }
13500
                break;
13501
            case 'hotpotatoes':
13502
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13503
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13504
                $myrow = Database::fetch_array($result);
13505
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13506
                $last = count($pathname) - 1; // Making a correct name for the link.
13507
                $filename = $pathname[$last]; // Making a correct name for the link.
13508
                $ext = explode('.', $filename);
13509
                $ext = strtolower($ext[sizeof($ext) - 1]);
13510
                $myrow['path'] = rawurlencode($myrow['path']);
13511
                $output = $filename;
13512
                break;
13513
        }
13514
13515
        return stripslashes($output);
13516
    }
13517
13518
    /**
13519
     * Get the parent names for the current item.
13520
     *
13521
     * @param int $newItemId Optional. The item ID
13522
     *
13523
     * @return array
13524
     */
13525
    public function getCurrentItemParentNames($newItemId = 0)
13526
    {
13527
        $newItemId = $newItemId ?: $this->get_current_item_id();
13528
        $return = [];
13529
        $item = $this->getItem($newItemId);
13530
        $parent = $this->getItem($item->get_parent());
13531
13532
        while ($parent) {
13533
            $return[] = $parent->get_title();
13534
13535
            $parent = $this->getItem($parent->get_parent());
13536
        }
13537
13538
        return array_reverse($return);
13539
    }
13540
13541
    /**
13542
     * Reads and process "lp_subscription_settings" setting.
13543
     *
13544
     * @return array
13545
     */
13546
    public static function getSubscriptionSettings()
13547
    {
13548
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13549
        if (empty($subscriptionSettings)) {
13550
            // By default allow both settings
13551
            $subscriptionSettings = [
13552
                'allow_add_users_to_lp' => true,
13553
                'allow_add_users_to_lp_category' => true,
13554
            ];
13555
        } else {
13556
            $subscriptionSettings = $subscriptionSettings['options'];
13557
        }
13558
13559
        return $subscriptionSettings;
13560
    }
13561
13562
    /**
13563
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13564
     */
13565
    public function exportToCourseBuildFormat()
13566
    {
13567
        if (!api_is_allowed_to_edit()) {
13568
            return false;
13569
        }
13570
13571
        $courseBuilder = new CourseBuilder();
13572
        $itemList = [];
13573
        /** @var learnpathItem $item */
13574
        foreach ($this->items as $item) {
13575
            $itemList[$item->get_type()][] = $item->get_path();
13576
        }
13577
13578
        if (empty($itemList)) {
13579
            return false;
13580
        }
13581
13582
        if (isset($itemList['document'])) {
13583
            // Get parents
13584
            foreach ($itemList['document'] as $documentId) {
13585
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13586
                if (!empty($documentInfo['parents'])) {
13587
                    foreach ($documentInfo['parents'] as $parentInfo) {
13588
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13589
                            continue;
13590
                        }
13591
                        $itemList['document'][] = $parentInfo['iid'];
13592
                    }
13593
                }
13594
            }
13595
13596
            $courseInfo = api_get_course_info();
13597
            foreach ($itemList['document'] as $documentId) {
13598
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13599
                $items = DocumentManager::get_resources_from_source_html(
13600
                    $documentInfo['absolute_path'],
13601
                    true,
13602
                    TOOL_DOCUMENT
13603
                );
13604
13605
                if (!empty($items)) {
13606
                    foreach ($items as $item) {
13607
                        // Get information about source url
13608
                        $url = $item[0]; // url
13609
                        $scope = $item[1]; // scope (local, remote)
13610
                        $type = $item[2]; // type (rel, abs, url)
13611
13612
                        $origParseUrl = parse_url($url);
13613
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13614
13615
                        if ($scope == 'local') {
13616
                            if ($type == 'abs' || $type == 'rel') {
13617
                                $documentFile = strstr($realOrigPath, 'document');
13618
                                if (strpos($realOrigPath, $documentFile) !== false) {
13619
                                    $documentFile = str_replace('document', '', $documentFile);
13620
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13621
                                    // Document found! Add it to the list
13622
                                    if ($itemDocumentId) {
13623
                                        $itemList['document'][] = $itemDocumentId;
13624
                                    }
13625
                                }
13626
                            }
13627
                        }
13628
                    }
13629
                }
13630
            }
13631
13632
            $courseBuilder->build_documents(
13633
                api_get_session_id(),
13634
                $this->get_course_int_id(),
13635
                true,
13636
                $itemList['document']
13637
            );
13638
        }
13639
13640
        if (isset($itemList['quiz'])) {
13641
            $courseBuilder->build_quizzes(
13642
                api_get_session_id(),
13643
                $this->get_course_int_id(),
13644
                true,
13645
                $itemList['quiz']
13646
            );
13647
        }
13648
13649
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13650
13651
        /*if (!empty($itemList['thread'])) {
13652
            $postList = [];
13653
            foreach ($itemList['thread'] as $postId) {
13654
                $post = get_post_information($postId);
13655
                if ($post) {
13656
                    if (!isset($itemList['forum'])) {
13657
                        $itemList['forum'] = [];
13658
                    }
13659
                    $itemList['forum'][] = $post['forum_id'];
13660
                    $postList[] = $postId;
13661
                }
13662
            }
13663
13664
            if (!empty($postList)) {
13665
                $courseBuilder->build_forum_posts(
13666
                    $this->get_course_int_id(),
13667
                    null,
13668
                    null,
13669
                    $postList
13670
                );
13671
            }
13672
        }*/
13673
13674
        if (!empty($itemList['thread'])) {
13675
            $threadList = [];
13676
            $em = Database::getManager();
13677
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13678
            foreach ($itemList['thread'] as $threadId) {
13679
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13680
                $thread = $repo->find($threadId);
13681
                if ($thread) {
13682
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13683
                    $threadList[] = $thread->getIid();
13684
                }
13685
            }
13686
13687
            if (!empty($threadList)) {
13688
                $courseBuilder->build_forum_topics(
13689
                    api_get_session_id(),
13690
                    $this->get_course_int_id(),
13691
                    null,
13692
                    $threadList
13693
                );
13694
            }
13695
        }
13696
13697
        $forumCategoryList = [];
13698
        if (isset($itemList['forum'])) {
13699
            foreach ($itemList['forum'] as $forumId) {
13700
                $forumInfo = get_forums($forumId);
13701
                $forumCategoryList[] = $forumInfo['forum_category'];
13702
            }
13703
        }
13704
13705
        if (!empty($forumCategoryList)) {
13706
            $courseBuilder->build_forum_category(
13707
                api_get_session_id(),
13708
                $this->get_course_int_id(),
13709
                true,
13710
                $forumCategoryList
13711
            );
13712
        }
13713
13714
        if (!empty($itemList['forum'])) {
13715
            $courseBuilder->build_forums(
13716
                api_get_session_id(),
13717
                $this->get_course_int_id(),
13718
                true,
13719
                $itemList['forum']
13720
            );
13721
        }
13722
13723
        if (isset($itemList['link'])) {
13724
            $courseBuilder->build_links(
13725
                api_get_session_id(),
13726
                $this->get_course_int_id(),
13727
                true,
13728
                $itemList['link']
13729
            );
13730
        }
13731
13732
        if (!empty($itemList['student_publication'])) {
13733
            $courseBuilder->build_works(
13734
                api_get_session_id(),
13735
                $this->get_course_int_id(),
13736
                true,
13737
                $itemList['student_publication']
13738
            );
13739
        }
13740
13741
        $courseBuilder->build_learnpaths(
13742
            api_get_session_id(),
13743
            $this->get_course_int_id(),
13744
            true,
13745
            [$this->get_id()],
13746
            false
13747
        );
13748
13749
        $courseBuilder->restoreDocumentsFromList();
13750
13751
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13752
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13753
        $result = DocumentManager::file_send_for_download(
13754
            $zipPath,
13755
            true,
13756
            $this->get_name().'.zip'
13757
        );
13758
13759
        if ($result) {
13760
            api_not_allowed();
13761
        }
13762
13763
        return true;
13764
    }
13765
13766
    /**
13767
     * Get the depth level of LP item.
13768
     *
13769
     * @param array $items
13770
     * @param int   $currentItemId
13771
     *
13772
     * @return int
13773
     */
13774
    private static function get_level_for_item($items, $currentItemId)
13775
    {
13776
        $parentItemId = 0;
13777
        if (isset($items[$currentItemId])) {
13778
            $parentItemId = $items[$currentItemId]->parent;
13779
        }
13780
13781
        if ($parentItemId == 0) {
13782
            return 0;
13783
        } else {
13784
            return self::get_level_for_item($items, $parentItemId) + 1;
13785
        }
13786
    }
13787
13788
    /**
13789
     * Generate the link for a learnpath category as course tool.
13790
     *
13791
     * @param int $categoryId
13792
     *
13793
     * @return string
13794
     */
13795
    private static function getCategoryLinkForTool($categoryId)
13796
    {
13797
        $categoryId = (int) $categoryId;
13798
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13799
            .http_build_query(
13800
                [
13801
                    'action' => 'view_category',
13802
                    'id' => $categoryId,
13803
                ]
13804
            );
13805
13806
        return $link;
13807
    }
13808
13809
    /**
13810
     * Return the scorm item type object with spaces replaced with _
13811
     * The return result is use to build a css classname like scorm_type_$return.
13812
     *
13813
     * @param $in_type
13814
     *
13815
     * @return mixed
13816
     */
13817
    private static function format_scorm_type_item($in_type)
13818
    {
13819
        return str_replace(' ', '_', $in_type);
13820
    }
13821
13822
    /**
13823
     * Check and obtain the lp final item if exist.
13824
     *
13825
     * @return learnpathItem
13826
     */
13827
    private function getFinalItem()
13828
    {
13829
        if (empty($this->items)) {
13830
            return null;
13831
        }
13832
13833
        foreach ($this->items as $item) {
13834
            if ($item->type !== 'final_item') {
13835
                continue;
13836
            }
13837
13838
            return $item;
13839
        }
13840
    }
13841
13842
    /**
13843
     * Get the LP Final Item Template.
13844
     *
13845
     * @return string
13846
     */
13847
    private function getFinalItemTemplate()
13848
    {
13849
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13850
    }
13851
13852
    /**
13853
     * Get the LP Final Item Url.
13854
     *
13855
     * @return string
13856
     */
13857
    private function getSavedFinalItem()
13858
    {
13859
        $finalItem = $this->getFinalItem();
13860
        $doc = DocumentManager::get_document_data_by_id(
13861
            $finalItem->path,
13862
            $this->cc
13863
        );
13864
        if ($doc && file_exists($doc['absolute_path'])) {
13865
            return file_get_contents($doc['absolute_path']);
13866
        }
13867
13868
        return '';
13869
    }
13870
}
13871