Passed
Push — master ( 271415...ff556a )
by Julito
09:26
created

learnpath   F

Complexity

Total Complexity 1870

Size/Duplication

Total Lines 13921
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7783
dl 0
loc 13921
rs 0.8
c 5
b 1
f 0
wmc 1870

215 Methods

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

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\CItemProperty;
9
use Chamilo\CourseBundle\Entity\CLp;
10
use Chamilo\CourseBundle\Entity\CLpCategory;
11
use Chamilo\CourseBundle\Entity\CLpItem;
12
use Chamilo\CourseBundle\Entity\CLpItemView;
13
use Chamilo\CourseBundle\Entity\CTool;
14
use Chamilo\UserBundle\Entity\User;
15
use ChamiloSession as Session;
16
use Gedmo\Sortable\Entity\Repository\SortableRepository;
17
use Symfony\Component\Filesystem\Filesystem;
18
use Symfony\Component\Finder\Finder;
19
20
/**
21
 * Class learnpath
22
 * This class defines the parent attributes and methods for Chamilo learnpaths
23
 * and SCORM learnpaths. It is used by the scorm class.
24
 *
25
 * @todo decouple class
26
 *
27
 * @package chamilo.learnpath
28
 *
29
 * @author  Yannick Warnier <[email protected]>
30
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
31
 */
32
class learnpath
33
{
34
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
35
36
    public $attempt = 0; // The number for the current ID view.
37
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
38
    public $current; // Id of the current item the user is viewing.
39
    public $current_score; // The score of the current item.
40
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
41
    public $current_time_stop; // The time the user closed this resource.
42
    public $default_status = 'not attempted';
43
    public $encoding = 'UTF-8';
44
    public $error = '';
45
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
46
    public $index; // The index of the active learnpath_item in $ordered_items array.
47
    public $items = [];
48
    public $last; // item_id of last item viewed in the learning path.
49
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
50
    public $license; // Which license this course has been given - not used yet on 20060522.
51
    public $lp_id; // DB iid for this learnpath.
52
    public $lp_view_id; // DB ID for lp_view
53
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
54
    public $message = '';
55
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
56
    public $name; // Learnpath name (they generally have one).
57
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
58
    public $path = ''; // Path inside the scorm directory (if scorm).
59
    public $theme; // The current theme of the learning path.
60
    public $preview_image; // The current image of the learning path.
61
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
62
    public $accumulateWorkTime; // The min time of learnpath
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
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
167
168
                if (!empty($row['publicated_on'])) {
169
                    $this->publicated_on = $row['publicated_on'];
170
                }
171
172
                if (!empty($row['expired_on'])) {
173
                    $this->expired_on = $row['expired_on'];
174
                }
175
                if ($this->type == 2) {
176
                    if ($row['force_commit'] == 1) {
177
                        $this->force_commit = true;
178
                    }
179
                }
180
                $this->mode = $row['default_view_mod'];
181
182
                // Check user ID.
183
                if (empty($user_id)) {
184
                    $this->error = 'User ID is empty';
185
                } else {
186
                    $userInfo = api_get_user_info($user_id);
187
                    if (!empty($userInfo)) {
188
                        $this->user_id = $userInfo['user_id'];
189
                    } else {
190
                        $this->error = 'User ID does not exist in database #'.$user_id;
191
                    }
192
                }
193
194
                // End of variables checking.
195
                $session_id = api_get_session_id();
196
                //  Get the session condition for learning paths of the base + session.
197
                $session = api_get_session_condition($session_id);
198
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
199
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
200
201
                // Selecting by view_count descending allows to get the highest view_count first.
202
                $sql = "SELECT * FROM $lp_table
203
                        WHERE 
204
                            c_id = $course_id AND 
205
                            lp_id = $lp_id AND 
206
                            user_id = $user_id 
207
                            $session
208
                        ORDER BY view_count DESC";
209
                $res = Database::query($sql);
210
                if ($debug) {
211
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
212
                }
213
214
                if (Database::num_rows($res) > 0) {
215
                    if ($debug) {
216
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
217
                    }
218
                    $row = Database::fetch_array($res);
219
                    $this->attempt = $row['view_count'];
220
                    $this->lp_view_id = $row['id'];
221
                    $this->last_item_seen = $row['last_item'];
222
                    $this->progress_db = $row['progress'];
223
                    $this->lp_view_session_id = $row['session_id'];
224
                } elseif (!api_is_invitee()) {
225
                    if ($debug) {
226
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
227
                    }
228
                    $this->attempt = 1;
229
                    $params = [
230
                        'c_id' => $course_id,
231
                        'lp_id' => $lp_id,
232
                        'user_id' => $user_id,
233
                        'view_count' => 1,
234
                        'session_id' => $session_id,
235
                        'last_item' => 0,
236
                    ];
237
                    $this->last_item_seen = 0;
238
                    $this->lp_view_session_id = $session_id;
239
                    $this->lp_view_id = Database::insert($lp_table, $params);
240
                    if (!empty($this->lp_view_id)) {
241
                        $sql = "UPDATE $lp_table SET id = iid
242
                                WHERE iid = ".$this->lp_view_id;
243
                        Database::query($sql);
244
                    }
245
                }
246
247
                // Initialise items.
248
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
249
                $sql = "SELECT * FROM $lp_item_table
250
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
251
                        ORDER BY parent_item_id, display_order";
252
                $res = Database::query($sql);
253
254
                if ($debug) {
255
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
256
                    error_log('-- Start while--');
257
                }
258
259
                $lp_item_id_list = [];
260
                while ($row = Database::fetch_array($res)) {
261
                    $lp_item_id_list[] = $row['iid'];
262
                    switch ($this->type) {
263
                        case 3: //aicc
264
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
265
                            if (is_object($oItem)) {
266
                                $my_item_id = $oItem->get_id();
267
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
268
                                $oItem->set_prevent_reinit($this->prevent_reinit);
269
                                // Don't use reference here as the next loop will make the pointed object change.
270
                                $this->items[$my_item_id] = $oItem;
271
                                $this->refs_list[$oItem->ref] = $my_item_id;
272
                                if ($debug) {
273
                                    error_log(
274
                                        'learnpath::__construct() - '.
275
                                        'aicc object with id '.$my_item_id.
276
                                        ' set in items[]',
277
                                        0
278
                                    );
279
                                }
280
                            }
281
                            break;
282
                        case 2:
283
                            $oItem = new scormItem('db', $row['iid'], $course_id);
284
                            if (is_object($oItem)) {
285
                                $my_item_id = $oItem->get_id();
286
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
287
                                $oItem->set_prevent_reinit($this->prevent_reinit);
288
                                // Don't use reference here as the next loop will make the pointed object change.
289
                                $this->items[$my_item_id] = $oItem;
290
                                $this->refs_list[$oItem->ref] = $my_item_id;
291
                                if ($debug) {
292
                                    error_log('object with id '.$my_item_id.' set in items[]');
293
                                }
294
                            }
295
                            break;
296
                        case 1:
297
                        default:
298
                            if ($debug) {
299
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
300
                            }
301
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
302
303
                            if ($debug) {
304
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
305
                            }
306
                            if (is_object($oItem)) {
307
                                $my_item_id = $oItem->get_id();
308
                                // Moved down to when we are sure the item_view exists.
309
                                //$oItem->set_lp_view($this->lp_view_id);
310
                                $oItem->set_prevent_reinit($this->prevent_reinit);
311
                                // Don't use reference here as the next loop will make the pointed object change.
312
                                $this->items[$my_item_id] = $oItem;
313
                                $this->refs_list[$my_item_id] = $my_item_id;
314
                                if ($debug) {
315
                                    error_log(
316
                                        'learnpath::__construct() '.__LINE__.
317
                                        ' - object with id '.$my_item_id.' set in items[]'
318
                                    );
319
                                }
320
                            }
321
                            break;
322
                    }
323
324
                    // Setting the object level with variable $this->items[$i][parent]
325
                    foreach ($this->items as $itemLPObject) {
326
                        $level = self::get_level_for_item(
327
                            $this->items,
328
                            $itemLPObject->db_id
329
                        );
330
                        $itemLPObject->level = $level;
331
                    }
332
333
                    // Setting the view in the item object.
334
                    if (is_object($this->items[$row['iid']])) {
335
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
336
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
337
                            $this->items[$row['iid']]->current_start_time = 0;
338
                            $this->items[$row['iid']]->current_stop_time = 0;
339
                        }
340
                    }
341
                }
342
343
                if ($debug) {
344
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
345
                }
346
347
                if (!empty($lp_item_id_list)) {
348
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
349
                    if (!empty($lp_item_id_list_to_string)) {
350
                        // Get last viewing vars.
351
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
352
                        // This query should only return one or zero result.
353
                        $sql = "SELECT lp_item_id, status
354
                                FROM $itemViewTable
355
                                WHERE
356
                                    c_id = $course_id AND
357
                                    lp_view_id = ".$this->get_view_id()." AND
358
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
359
                                ORDER BY view_count DESC ";
360
361
                        if ($debug) {
362
                            error_log(
363
                                'learnpath::__construct() - Selecting item_views: '.$sql,
364
                                0
365
                            );
366
                        }
367
368
                        $status_list = [];
369
                        $res = Database::query($sql);
370
                        while ($row = Database:: fetch_array($res)) {
371
                            $status_list[$row['lp_item_id']] = $row['status'];
372
                        }
373
374
                        foreach ($lp_item_id_list as $item_id) {
375
                            if (isset($status_list[$item_id])) {
376
                                $status = $status_list[$item_id];
377
                                if (is_object($this->items[$item_id])) {
378
                                    $this->items[$item_id]->set_status($status);
379
                                    if (empty($status)) {
380
                                        $this->items[$item_id]->set_status(
381
                                            $this->default_status
382
                                        );
383
                                    }
384
                                }
385
                            } else {
386
                                if (!api_is_invitee()) {
387
                                    if (is_object($this->items[$item_id])) {
388
                                        $this->items[$item_id]->set_status(
389
                                            $this->default_status
390
                                        );
391
                                    }
392
393
                                    if (!empty($this->lp_view_id)) {
394
                                        // Add that row to the lp_item_view table so that
395
                                        // we have something to show in the stats page.
396
                                        $params = [
397
                                            'c_id' => $course_id,
398
                                            'lp_item_id' => $item_id,
399
                                            'lp_view_id' => $this->lp_view_id,
400
                                            'view_count' => 1,
401
                                            'status' => 'not attempted',
402
                                            'start_time' => time(),
403
                                            'total_time' => 0,
404
                                            'score' => 0,
405
                                        ];
406
                                        $insertId = Database::insert($itemViewTable, $params);
407
408
                                        if ($insertId) {
409
                                            $sql = "UPDATE $itemViewTable SET id = iid
410
                                                    WHERE iid = $insertId";
411
                                            Database::query($sql);
412
                                        }
413
414
                                        $this->items[$item_id]->set_lp_view(
415
                                            $this->lp_view_id,
416
                                            $course_id
417
                                        );
418
                                    }
419
                                }
420
                            }
421
                        }
422
                    }
423
                }
424
425
                $this->ordered_items = self::get_flat_ordered_items_list(
426
                    $this->get_id(),
427
                    0,
428
                    $course_id
429
                );
430
                $this->max_ordered_items = 0;
431
                foreach ($this->ordered_items as $index => $dummy) {
432
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
433
                        $this->max_ordered_items = $index;
434
                    }
435
                }
436
                // TODO: Define the current item better.
437
                $this->first();
438
                if ($debug) {
439
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
440
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
441
                }
442
            } else {
443
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
444
            }
445
        }
446
    }
447
448
    /**
449
     * @return string
450
     */
451
    public function getCourseCode()
452
    {
453
        return $this->cc;
454
    }
455
456
    /**
457
     * @return int
458
     */
459
    public function get_course_int_id()
460
    {
461
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
462
    }
463
464
    /**
465
     * @param $course_id
466
     *
467
     * @return int
468
     */
469
    public function set_course_int_id($course_id)
470
    {
471
        return $this->course_int_id = (int) $course_id;
472
    }
473
474
    /**
475
     * Function rewritten based on old_add_item() from Yannick Warnier.
476
     * Due the fact that users can decide where the item should come, I had to overlook this function and
477
     * I found it better to rewrite it. Old function is still available.
478
     * Added also the possibility to add a description.
479
     *
480
     * @param int    $parent
481
     * @param int    $previous
482
     * @param string $type
483
     * @param int    $id               resource ID (ref)
484
     * @param string $title
485
     * @param string $description
486
     * @param int    $prerequisites
487
     * @param int    $max_time_allowed
488
     * @param int    $userId
489
     *
490
     * @return int
491
     */
492
    public function add_item(
493
        $parent,
494
        $previous,
495
        $type = 'dir',
496
        $id,
497
        $title,
498
        $description,
499
        $prerequisites = 0,
500
        $max_time_allowed = 0,
501
        $userId = 0
502
    ) {
503
        $course_id = $this->course_info['real_id'];
504
        if ($this->debug > 0) {
505
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
506
        }
507
        if (empty($course_id)) {
508
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
509
            $this->course_info = api_get_course_info($this->cc);
510
            $course_id = $this->course_info['real_id'];
511
        }
512
        $userId = empty($userId) ? api_get_user_id() : $userId;
513
        $sessionId = api_get_session_id();
514
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
515
        $_course = $this->course_info;
516
        $parent = (int) $parent;
517
        $previous = (int) $previous;
518
        $id = (int) $id;
519
        $max_time_allowed = htmlentities($max_time_allowed);
520
        if (empty($max_time_allowed)) {
521
            $max_time_allowed = 0;
522
        }
523
        $sql = "SELECT COUNT(iid) AS num
524
                FROM $tbl_lp_item
525
                WHERE
526
                    c_id = $course_id AND
527
                    lp_id = ".$this->get_id()." AND
528
                    parent_item_id = ".$parent;
529
530
        $res_count = Database::query($sql);
531
        $row = Database::fetch_array($res_count);
532
        $num = $row['num'];
533
534
        $tmp_previous = 0;
535
        $display_order = 0;
536
        $next = 0;
537
        if ($num > 0) {
538
            if (empty($previous)) {
539
                $sql = "SELECT iid, next_item_id, display_order
540
                        FROM $tbl_lp_item
541
                        WHERE
542
                            c_id = $course_id AND
543
                            lp_id = ".$this->get_id()." AND
544
                            parent_item_id = $parent AND
545
                            previous_item_id = 0 OR
546
                            previous_item_id = $parent";
547
                $result = Database::query($sql);
548
                $row = Database::fetch_array($result);
549
                if ($row) {
550
                    $next = $row['iid'];
551
                }
552
            } else {
553
                $previous = (int) $previous;
554
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
555
						FROM $tbl_lp_item
556
                        WHERE
557
                            c_id = $course_id AND
558
                            lp_id = ".$this->get_id()." AND
559
                            id = $previous";
560
                $result = Database::query($sql);
561
                $row = Database::fetch_array($result);
562
                if ($row) {
563
                    $tmp_previous = $row['iid'];
564
                    $next = $row['next_item_id'];
565
                    $display_order = $row['display_order'];
566
                }
567
            }
568
        }
569
570
        $id = (int) $id;
571
        $typeCleaned = Database::escape_string($type);
572
        $max_score = 100;
573
        if ($type === 'quiz') {
574
            $sql = 'SELECT SUM(ponderation)
575
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
576
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
577
                    ON
578
                        quiz_question.id = quiz_rel_question.question_id AND
579
                        quiz_question.c_id = quiz_rel_question.c_id
580
                    WHERE
581
                        quiz_rel_question.exercice_id = '.$id." AND
582
                        quiz_question.c_id = $course_id AND
583
                        quiz_rel_question.c_id = $course_id ";
584
            $rsQuiz = Database::query($sql);
585
            $max_score = Database::result($rsQuiz, 0, 0);
586
587
            // Disabling the exercise if we add it inside a LP
588
            $exercise = new Exercise($course_id);
589
            $exercise->read($id);
590
            $exercise->disable();
591
            $exercise->save();
592
        }
593
594
        $params = [
595
            'c_id' => $course_id,
596
            'lp_id' => $this->get_id(),
597
            'item_type' => $typeCleaned,
598
            'ref' => '',
599
            'title' => $title,
600
            'description' => $description,
601
            'path' => $id,
602
            'max_score' => $max_score,
603
            'parent_item_id' => $parent,
604
            'previous_item_id' => $previous,
605
            'next_item_id' => (int) $next,
606
            'display_order' => $display_order + 1,
607
            'prerequisite' => $prerequisites,
608
            'max_time_allowed' => $max_time_allowed,
609
            'min_score' => 0,
610
            'launch_data' => '',
611
        ];
612
613
        if ($prerequisites != 0) {
614
            $params['prerequisite'] = $prerequisites;
615
        }
616
617
        $new_item_id = Database::insert($tbl_lp_item, $params);
618
        if ($new_item_id) {
619
            if ($this->debug > 2) {
620
                error_log('Inserting dir/chapter: '.$new_item_id, 0);
621
            }
622
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
623
            Database::query($sql);
624
625
            if (!empty($next)) {
626
                $sql = "UPDATE $tbl_lp_item
627
                        SET previous_item_id = $new_item_id 
628
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
629
                Database::query($sql);
630
            }
631
632
            // Update the item that should be before the new item.
633
            if (!empty($tmp_previous)) {
634
                $sql = "UPDATE $tbl_lp_item
635
                        SET next_item_id = $new_item_id
636
                        WHERE c_id = $course_id AND id = $tmp_previous";
637
                Database::query($sql);
638
            }
639
640
            // Update all the items after the new item.
641
            $sql = "UPDATE $tbl_lp_item
642
                        SET display_order = display_order + 1
643
                    WHERE
644
                        c_id = $course_id AND
645
                        lp_id = ".$this->get_id()." AND
646
                        iid <> $new_item_id AND
647
                        parent_item_id = $parent AND
648
                        display_order > $display_order";
649
            Database::query($sql);
650
651
            // Update the item that should come after the new item.
652
            $sql = "UPDATE $tbl_lp_item
653
                    SET ref = $new_item_id
654
                    WHERE c_id = $course_id AND iid = $new_item_id";
655
            Database::query($sql);
656
657
            $sql = "UPDATE $tbl_lp_item
658
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
659
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
660
            Database::query($sql);
661
662
            // Upload audio.
663
            if (!empty($_FILES['mp3']['name'])) {
664
                // Create the audio folder if it does not exist yet.
665
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
666
                if (!is_dir($filepath.'audio')) {
667
                    mkdir(
668
                        $filepath.'audio',
669
                        api_get_permissions_for_new_directories()
670
                    );
671
                    $audio_id = DocumentManager::addDocument(
672
                        $_course,
673
                        '/audio',
674
                        'folder',
675
                        0,
676
                        'audio',
677
                        '',
678
                        0,
679
                        true,
680
                        null,
681
                        $sessionId,
682
                        $userId
683
                    );
684
                }
685
686
                $file_path = handle_uploaded_document(
687
                    $_course,
688
                    $_FILES['mp3'],
689
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
690
                    '/audio',
691
                    $userId,
692
                    '',
693
                    '',
694
                    '',
695
                    '',
696
                    false
697
                );
698
699
                // Getting the filename only.
700
                $file_components = explode('/', $file_path);
701
                $file = $file_components[count($file_components) - 1];
702
703
                // Store the mp3 file in the lp_item table.
704
                $sql = "UPDATE $tbl_lp_item SET
705
                          audio = '".Database::escape_string($file)."'
706
                        WHERE iid = '".intval($new_item_id)."'";
707
                Database::query($sql);
708
            }
709
        }
710
711
        return $new_item_id;
712
    }
713
714
    /**
715
     * Static admin function allowing addition of a learnpath to a course.
716
     *
717
     * @param string $courseCode
718
     * @param string $name
719
     * @param string $description
720
     * @param string $learnpath
721
     * @param string $origin
722
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
723
     * @param string $publicated_on
724
     * @param string $expired_on
725
     * @param int    $categoryId
726
     * @param int    $userId
727
     *
728
     * @return int The new learnpath ID on success, 0 on failure
729
     */
730
    public static function add_lp(
731
        $courseCode,
732
        $name,
733
        $description = '',
734
        $learnpath = 'guess',
735
        $origin = 'zip',
736
        $zipname = '',
737
        $publicated_on = '',
738
        $expired_on = '',
739
        $categoryId = 0,
740
        $userId = 0
741
    ) {
742
        global $charset;
743
744
        if (!empty($courseCode)) {
745
            $courseInfo = api_get_course_info($courseCode);
746
            $course_id = $courseInfo['real_id'];
747
        } else {
748
            $course_id = api_get_course_int_id();
749
            $courseInfo = api_get_course_info();
750
        }
751
752
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
753
        // Check course code exists.
754
        // Check lp_name doesn't exist, otherwise append something.
755
        $i = 0;
756
        $name = Database::escape_string($name);
757
        $categoryId = (int) $categoryId;
758
759
        // Session id.
760
        $session_id = api_get_session_id();
761
        $userId = empty($userId) ? api_get_user_id() : $userId;
762
        $check_name = "SELECT * FROM $tbl_lp
763
                       WHERE c_id = $course_id AND name = '$name'";
764
765
        $res_name = Database::query($check_name);
766
767
        if (empty($publicated_on)) {
768
            $publicated_on = null;
769
        } else {
770
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
771
        }
772
773
        if (empty($expired_on)) {
774
            $expired_on = null;
775
        } else {
776
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
777
        }
778
779
        while (Database::num_rows($res_name)) {
780
            // There is already one such name, update the current one a bit.
781
            $i++;
782
            $name = $name.' - '.$i;
783
            $check_name = "SELECT * FROM $tbl_lp 
784
                           WHERE c_id = $course_id AND name = '$name'";
785
            $res_name = Database::query($check_name);
786
        }
787
        // New name does not exist yet; keep it.
788
        // Escape description.
789
        // Kevin: added htmlentities().
790
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
791
        $type = 1;
792
        switch ($learnpath) {
793
            case 'guess':
794
                break;
795
            case 'dokeos':
796
            case 'chamilo':
797
                $type = 1;
798
                break;
799
            case 'aicc':
800
                break;
801
        }
802
803
        switch ($origin) {
804
            case 'zip':
805
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
806
                break;
807
            case 'manual':
808
            default:
809
                $get_max = "SELECT MAX(display_order) 
810
                            FROM $tbl_lp WHERE c_id = $course_id";
811
                $res_max = Database::query($get_max);
812
                if (Database::num_rows($res_max) < 1) {
813
                    $dsp = 1;
814
                } else {
815
                    $row = Database::fetch_array($res_max);
816
                    $dsp = $row[0] + 1;
817
                }
818
819
                $params = [
820
                    'c_id' => $course_id,
821
                    'lp_type' => $type,
822
                    'name' => $name,
823
                    'description' => $description,
824
                    'path' => '',
825
                    'default_view_mod' => 'embedded',
826
                    'default_encoding' => 'UTF-8',
827
                    'display_order' => $dsp,
828
                    'content_maker' => 'Chamilo',
829
                    'content_local' => 'local',
830
                    'js_lib' => '',
831
                    'session_id' => $session_id,
832
                    'created_on' => api_get_utc_datetime(),
833
                    'modified_on' => api_get_utc_datetime(),
834
                    'publicated_on' => $publicated_on,
835
                    'expired_on' => $expired_on,
836
                    'category_id' => $categoryId,
837
                    'force_commit' => 0,
838
                    'content_license' => '',
839
                    'debug' => 0,
840
                    'theme' => '',
841
                    'preview_image' => '',
842
                    'author' => '',
843
                    'prerequisite' => 0,
844
                    'hide_toc_frame' => 0,
845
                    'seriousgame_mode' => 0,
846
                    'autolaunch' => 0,
847
                    'max_attempts' => 0,
848
                    'subscribe_users' => 0,
849
                    'accumulate_scorm_time' => 1,
850
                ];
851
                $id = Database::insert($tbl_lp, $params);
852
853
                if ($id > 0) {
854
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
855
                    Database::query($sql);
856
857
                    // Insert into item_property.
858
                    api_item_property_update(
859
                        $courseInfo,
860
                        TOOL_LEARNPATH,
861
                        $id,
862
                        'LearnpathAdded',
863
                        $userId
864
                    );
865
                    api_set_default_visibility(
866
                        $id,
867
                        TOOL_LEARNPATH,
868
                        0,
869
                        $courseInfo,
870
                        $session_id,
871
                        $userId
872
                    );
873
874
                    return $id;
875
                }
876
                break;
877
        }
878
    }
879
880
    /**
881
     * Auto completes the parents of an item in case it's been completed or passed.
882
     *
883
     * @param int $item Optional ID of the item from which to look for parents
884
     */
885
    public function autocomplete_parents($item)
886
    {
887
        $debug = $this->debug;
888
889
        if ($debug) {
890
            error_log('Learnpath::autocomplete_parents()');
891
        }
892
893
        if (empty($item)) {
894
            $item = $this->current;
895
        }
896
897
        $currentItem = $this->getItem($item);
898
        if ($currentItem) {
899
            $parent_id = $currentItem->get_parent();
900
            $parent = $this->getItem($parent_id);
901
            if ($parent) {
902
                // if $item points to an object and there is a parent.
903
                if ($debug) {
904
                    error_log(
905
                        'Autocompleting parent of item '.$item.' '.
906
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
907
                        0
908
                    );
909
                }
910
911
                // New experiment including failed and browsed in completed status.
912
                //$current_status = $currentItem->get_status();
913
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
914
                // Fixes chapter auto complete
915
                if (true) {
916
                    // If the current item is completed or passes or succeeded.
917
                    $updateParentStatus = true;
918
                    if ($debug) {
919
                        error_log('Status of current item is alright');
920
                    }
921
922
                    foreach ($parent->get_children() as $childItemId) {
923
                        $childItem = $this->getItem($childItemId);
924
925
                        // If children was not set try to get the info
926
                        if (empty($childItem->db_item_view_id)) {
927
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
928
                        }
929
930
                        // Check all his brothers (parent's children) for completion status.
931
                        if ($childItemId != $item) {
932
                            if ($debug) {
933
                                error_log(
934
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
935
                                    0
936
                                );
937
                            }
938
                            // Trying completing parents of failed and browsed items as well.
939
                            if ($childItem->status_is(
940
                                [
941
                                    'completed',
942
                                    'passed',
943
                                    'succeeded',
944
                                    'browsed',
945
                                    'failed',
946
                                ]
947
                            )
948
                            ) {
949
                                // Keep completion status to true.
950
                                continue;
951
                            } else {
952
                                if ($debug > 2) {
953
                                    error_log(
954
                                        '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,
955
                                        0
956
                                    );
957
                                }
958
                                $updateParentStatus = false;
959
                                break;
960
                            }
961
                        }
962
                    }
963
964
                    if ($updateParentStatus) {
965
                        // If all the children were completed:
966
                        $parent->set_status('completed');
967
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
968
                        // Force the status to "completed"
969
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
970
                        $this->update_queue[$parent->get_id()] = 'completed';
971
                        if ($debug) {
972
                            error_log(
973
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
974
                                print_r($this->update_queue, 1),
975
                                0
976
                            );
977
                        }
978
                        // Recursive call.
979
                        $this->autocomplete_parents($parent->get_id());
980
                    }
981
                }
982
            } else {
983
                if ($debug) {
984
                    error_log("Parent #$parent_id does not exists");
985
                }
986
            }
987
        } else {
988
            if ($debug) {
989
                error_log("#$item is an item that doesn't have parents");
990
            }
991
        }
992
    }
993
994
    /**
995
     * Closes the current resource.
996
     *
997
     * Stops the timer
998
     * Saves into the database if required
999
     * Clears the current resource data from this object
1000
     *
1001
     * @return bool True on success, false on failure
1002
     */
1003
    public function close()
1004
    {
1005
        if ($this->debug > 0) {
1006
            error_log('In learnpath::close()', 0);
1007
        }
1008
        if (empty($this->lp_id)) {
1009
            $this->error = 'Trying to close this learnpath but no ID is set';
1010
1011
            return false;
1012
        }
1013
        $this->current_time_stop = time();
1014
        $this->ordered_items = [];
1015
        $this->index = 0;
1016
        unset($this->lp_id);
1017
        //unset other stuff
1018
        return true;
1019
    }
1020
1021
    /**
1022
     * Static admin function allowing removal of a learnpath.
1023
     *
1024
     * @param array  $courseInfo
1025
     * @param int    $id         Learnpath ID
1026
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1027
     *
1028
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1029
     */
1030
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1031
    {
1032
        $course_id = api_get_course_int_id();
1033
        if (!empty($courseInfo)) {
1034
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1035
        }
1036
1037
        // TODO: Implement a way of getting this to work when the current object is not set.
1038
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1039
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1040
        if (!empty($id) && ($id != $this->lp_id)) {
1041
            return false;
1042
        }
1043
1044
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1045
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1046
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1047
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1048
1049
        // Delete lp item id.
1050
        foreach ($this->items as $lpItemId => $dummy) {
1051
            $sql = "DELETE FROM $lp_item_view
1052
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1053
            Database::query($sql);
1054
        }
1055
1056
        // Proposed by Christophe (nickname: clefevre)
1057
        $sql = "DELETE FROM $lp_item
1058
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1059
        Database::query($sql);
1060
1061
        $sql = "DELETE FROM $lp_view 
1062
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1063
        Database::query($sql);
1064
1065
        self::toggle_publish($this->lp_id, 'i');
1066
1067
        if ($this->type == 2 || $this->type == 3) {
1068
            // This is a scorm learning path, delete the files as well.
1069
            $sql = "SELECT path FROM $lp
1070
                    WHERE iid = ".$this->lp_id;
1071
            $res = Database::query($sql);
1072
            if (Database::num_rows($res) > 0) {
1073
                $row = Database::fetch_array($res);
1074
                $path = $row['path'];
1075
                $sql = "SELECT id FROM $lp
1076
                        WHERE 
1077
                            c_id = $course_id AND
1078
                            path = '$path' AND 
1079
                            iid != ".$this->lp_id;
1080
                $res = Database::query($sql);
1081
                if (Database::num_rows($res) > 0) {
1082
                    // Another learning path uses this directory, so don't delete it.
1083
                    if ($this->debug > 2) {
1084
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1085
                    }
1086
                } else {
1087
                    // No other LP uses that directory, delete it.
1088
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1089
                    // The absolute system path for this course.
1090
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1091
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1092
                        if ($this->debug > 2) {
1093
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1094
                        }
1095
                        // Proposed by Christophe (clefevre).
1096
                        if (strcmp(substr($path, -2), "/.") == 0) {
1097
                            $path = substr($path, 0, -1); // Remove "." at the end.
1098
                        }
1099
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1100
                        rmdirr($course_scorm_dir.$path);
1101
                    }
1102
                }
1103
            }
1104
        }
1105
1106
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1107
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1108
        // Delete tools
1109
        $sql = "DELETE FROM $tbl_tool
1110
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1111
        Database::query($sql);
1112
1113
        $sql = "DELETE FROM $lp 
1114
                WHERE iid = ".$this->lp_id;
1115
        Database::query($sql);
1116
        // Updates the display order of all lps.
1117
        $this->update_display_order();
1118
1119
        api_item_property_update(
1120
            api_get_course_info(),
1121
            TOOL_LEARNPATH,
1122
            $this->lp_id,
1123
            'delete',
1124
            api_get_user_id()
1125
        );
1126
1127
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1128
            api_get_course_id(),
1129
            4,
1130
            $id,
1131
            api_get_session_id()
1132
        );
1133
1134
        if ($link_info !== false) {
1135
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1136
        }
1137
1138
        if (api_get_setting('search_enabled') == 'true') {
1139
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1140
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1141
        }
1142
    }
1143
1144
    /**
1145
     * Removes all the children of one item - dangerous!
1146
     *
1147
     * @param int $id Element ID of which children have to be removed
1148
     *
1149
     * @return int Total number of children removed
1150
     */
1151
    public function delete_children_items($id)
1152
    {
1153
        $course_id = $this->course_info['real_id'];
1154
        if ($this->debug > 0) {
1155
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1156
        }
1157
        $num = 0;
1158
        if (empty($id) || $id != strval(intval($id))) {
1159
            return false;
1160
        }
1161
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1162
        $sql = "SELECT * FROM $lp_item 
1163
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1164
        $res = Database::query($sql);
1165
        while ($row = Database::fetch_array($res)) {
1166
            $num += $this->delete_children_items($row['iid']);
1167
            $sql = "DELETE FROM $lp_item 
1168
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1169
            Database::query($sql);
1170
            $num++;
1171
        }
1172
1173
        return $num;
1174
    }
1175
1176
    /**
1177
     * Removes an item from the current learnpath.
1178
     *
1179
     * @param int $id Elem ID (0 if first)
1180
     *
1181
     * @return int Number of elements moved
1182
     *
1183
     * @todo implement resource removal
1184
     */
1185
    public function delete_item($id)
1186
    {
1187
        $course_id = api_get_course_int_id();
1188
        if ($this->debug > 0) {
1189
            error_log('In learnpath::delete_item()', 0);
1190
        }
1191
        // TODO: Implement the resource removal.
1192
        if (empty($id) || $id != strval(intval($id))) {
1193
            return false;
1194
        }
1195
        // First select item to get previous, next, and display order.
1196
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1197
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1198
        $res_sel = Database::query($sql_sel);
1199
        if (Database::num_rows($res_sel) < 1) {
1200
            return false;
1201
        }
1202
        $row = Database::fetch_array($res_sel);
1203
        $previous = $row['previous_item_id'];
1204
        $next = $row['next_item_id'];
1205
        $display = $row['display_order'];
1206
        $parent = $row['parent_item_id'];
1207
        $lp = $row['lp_id'];
1208
        // Delete children items.
1209
        $num = $this->delete_children_items($id);
1210
        if ($this->debug > 2) {
1211
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1212
        }
1213
        // Now delete the item.
1214
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1215
        if ($this->debug > 2) {
1216
            error_log('Deleting item: '.$sql_del, 0);
1217
        }
1218
        Database::query($sql_del);
1219
        // Now update surrounding items.
1220
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1221
                    WHERE iid = $previous";
1222
        Database::query($sql_upd);
1223
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1224
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1225
        Database::query($sql_upd);
1226
        // Now update all following items with new display order.
1227
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1228
                    WHERE 
1229
                        c_id = $course_id AND 
1230
                        lp_id = $lp AND 
1231
                        parent_item_id = $parent AND 
1232
                        display_order > $display";
1233
        Database::query($sql_all);
1234
1235
        //Removing prerequisites since the item will not longer exist
1236
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1237
                    WHERE c_id = $course_id AND prerequisite = $id";
1238
        Database::query($sql_all);
1239
1240
        $sql = "UPDATE $lp_item
1241
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
1242
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1243
        Database::query($sql);
1244
1245
        // Remove from search engine if enabled.
1246
        if (api_get_setting('search_enabled') === 'true') {
1247
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1248
            $sql = 'SELECT * FROM %s 
1249
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1250
                    LIMIT 1';
1251
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1252
            $res = Database::query($sql);
1253
            if (Database::num_rows($res) > 0) {
1254
                $row2 = Database::fetch_array($res);
1255
                $di = new ChamiloIndexer();
1256
                $di->remove_document($row2['search_did']);
1257
            }
1258
            $sql = 'DELETE FROM %s 
1259
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1260
                    LIMIT 1';
1261
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1262
            Database::query($sql);
1263
        }
1264
    }
1265
1266
    /**
1267
     * Updates an item's content in place.
1268
     *
1269
     * @param int    $id               Element ID
1270
     * @param int    $parent           Parent item ID
1271
     * @param int    $previous         Previous item ID
1272
     * @param string $title            Item title
1273
     * @param string $description      Item description
1274
     * @param string $prerequisites    Prerequisites (optional)
1275
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1276
     * @param int    $max_time_allowed
1277
     * @param string $url
1278
     *
1279
     * @return bool True on success, false on error
1280
     */
1281
    public function edit_item(
1282
        $id,
1283
        $parent,
1284
        $previous,
1285
        $title,
1286
        $description,
1287
        $prerequisites = '0',
1288
        $audio = [],
1289
        $max_time_allowed = 0,
1290
        $url = ''
1291
    ) {
1292
        $course_id = api_get_course_int_id();
1293
        $_course = api_get_course_info();
1294
        $id = (int) $id;
1295
1296
        if (empty($max_time_allowed)) {
1297
            $max_time_allowed = 0;
1298
        }
1299
1300
        if (empty($id) || empty($_course)) {
1301
            return false;
1302
        }
1303
1304
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1305
        $sql = "SELECT * FROM $tbl_lp_item 
1306
                WHERE iid = $id";
1307
        $res_select = Database::query($sql);
1308
        $row_select = Database::fetch_array($res_select);
1309
        $audio_update_sql = '';
1310
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1311
            // Create the audio folder if it does not exist yet.
1312
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1313
            if (!is_dir($filepath.'audio')) {
1314
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1315
                $audio_id = DocumentManager::addDocument(
1316
                    $_course,
1317
                    '/audio',
1318
                    'folder',
1319
                    0,
1320
                    'audio'
1321
                );
1322
            }
1323
1324
            // Upload file in documents.
1325
            $pi = pathinfo($audio['name']);
1326
            if ($pi['extension'] === 'mp3') {
1327
                $c_det = api_get_course_info($this->cc);
1328
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1329
                $path = handle_uploaded_document(
1330
                    $c_det,
1331
                    $audio,
1332
                    $bp,
1333
                    '/audio',
1334
                    api_get_user_id(),
1335
                    0,
1336
                    null,
1337
                    0,
1338
                    'rename',
1339
                    false,
1340
                    0
1341
                );
1342
                $path = substr($path, 7);
1343
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1344
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1345
            }
1346
        }
1347
1348
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1349
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1350
1351
        // TODO: htmlspecialchars to be checked for encoding related problems.
1352
        if ($same_parent && $same_previous) {
1353
            // Only update title and description.
1354
            $sql = "UPDATE $tbl_lp_item
1355
                    SET title = '".Database::escape_string($title)."',
1356
                        prerequisite = '".$prerequisites."',
1357
                        description = '".Database::escape_string($description)."'
1358
                        ".$audio_update_sql.",
1359
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1360
                    WHERE iid = $id";
1361
            Database::query($sql);
1362
        } else {
1363
            $old_parent = $row_select['parent_item_id'];
1364
            $old_previous = $row_select['previous_item_id'];
1365
            $old_next = $row_select['next_item_id'];
1366
            $old_order = $row_select['display_order'];
1367
            $old_prerequisite = $row_select['prerequisite'];
1368
            $old_max_time_allowed = $row_select['max_time_allowed'];
1369
1370
            /* BEGIN -- virtually remove the current item id */
1371
            /* for the next and previous item it is like the current item doesn't exist anymore */
1372
            if ($old_previous != 0) {
1373
                // Next
1374
                $sql = "UPDATE $tbl_lp_item
1375
                        SET next_item_id = $old_next
1376
                        WHERE iid = $old_previous";
1377
                Database::query($sql);
1378
            }
1379
1380
            if (!empty($old_next)) {
1381
                // Previous
1382
                $sql = "UPDATE $tbl_lp_item
1383
                        SET previous_item_id = $old_previous
1384
                        WHERE iid = $old_next";
1385
                Database::query($sql);
1386
            }
1387
1388
            // display_order - 1 for every item with a display_order
1389
            // bigger then the display_order of the current item.
1390
            $sql = "UPDATE $tbl_lp_item
1391
                    SET display_order = display_order - 1
1392
                    WHERE
1393
                        c_id = $course_id AND
1394
                        display_order > $old_order AND
1395
                        lp_id = ".$this->lp_id." AND
1396
                        parent_item_id = $old_parent";
1397
            Database::query($sql);
1398
            /* END -- virtually remove the current item id */
1399
1400
            /* BEGIN -- update the current item id to his new location */
1401
            if ($previous == 0) {
1402
                // Select the data of the item that should come after the current item.
1403
                $sql = "SELECT id, display_order
1404
                        FROM $tbl_lp_item
1405
                        WHERE
1406
                            c_id = $course_id AND
1407
                            lp_id = ".$this->lp_id." AND
1408
                            parent_item_id = $parent AND
1409
                            previous_item_id = $previous";
1410
                $res_select_old = Database::query($sql);
1411
                $row_select_old = Database::fetch_array($res_select_old);
1412
1413
                // If the new parent didn't have children before.
1414
                if (Database::num_rows($res_select_old) == 0) {
1415
                    $new_next = 0;
1416
                    $new_order = 1;
1417
                } else {
1418
                    $new_next = $row_select_old['id'];
1419
                    $new_order = $row_select_old['display_order'];
1420
                }
1421
            } else {
1422
                // Select the data of the item that should come before the current item.
1423
                $sql = "SELECT next_item_id, display_order
1424
                        FROM $tbl_lp_item
1425
                        WHERE iid = $previous";
1426
                $res_select_old = Database::query($sql);
1427
                $row_select_old = Database::fetch_array($res_select_old);
1428
                $new_next = $row_select_old['next_item_id'];
1429
                $new_order = $row_select_old['display_order'] + 1;
1430
            }
1431
1432
            // TODO: htmlspecialchars to be checked for encoding related problems.
1433
            // Update the current item with the new data.
1434
            $sql = "UPDATE $tbl_lp_item
1435
                    SET
1436
                        title = '".Database::escape_string($title)."',
1437
                        description = '".Database::escape_string($description)."',
1438
                        parent_item_id = $parent,
1439
                        previous_item_id = $previous,
1440
                        next_item_id = $new_next,
1441
                        display_order = $new_order
1442
                        $audio_update_sql
1443
                    WHERE iid = $id";
1444
            Database::query($sql);
1445
1446
            if ($previous != 0) {
1447
                // Update the previous item's next_item_id.
1448
                $sql = "UPDATE $tbl_lp_item
1449
                        SET next_item_id = $id
1450
                        WHERE iid = $previous";
1451
                Database::query($sql);
1452
            }
1453
1454
            if (!empty($new_next)) {
1455
                // Update the next item's previous_item_id.
1456
                $sql = "UPDATE $tbl_lp_item
1457
                        SET previous_item_id = $id
1458
                        WHERE iid = $new_next";
1459
                Database::query($sql);
1460
            }
1461
1462
            if ($old_prerequisite != $prerequisites) {
1463
                $sql = "UPDATE $tbl_lp_item
1464
                        SET prerequisite = '$prerequisites'
1465
                        WHERE iid = $id";
1466
                Database::query($sql);
1467
            }
1468
1469
            if ($old_max_time_allowed != $max_time_allowed) {
1470
                // update max time allowed
1471
                $sql = "UPDATE $tbl_lp_item
1472
                        SET max_time_allowed = $max_time_allowed
1473
                        WHERE iid = $id";
1474
                Database::query($sql);
1475
            }
1476
1477
            // Update all the items with the same or a bigger display_order than the current item.
1478
            $sql = "UPDATE $tbl_lp_item
1479
                    SET display_order = display_order + 1
1480
                    WHERE
1481
                       c_id = $course_id AND
1482
                       lp_id = ".$this->get_id()." AND
1483
                       iid <> $id AND
1484
                       parent_item_id = $parent AND
1485
                       display_order >= $new_order";
1486
            Database::query($sql);
1487
        }
1488
1489
        if ($row_select['item_type'] == 'link') {
1490
            $link = new Link();
1491
            $linkId = $row_select['path'];
1492
            $link->updateLink($linkId, $url);
1493
        }
1494
    }
1495
1496
    /**
1497
     * Updates an item's prereq in place.
1498
     *
1499
     * @param int    $id              Element ID
1500
     * @param string $prerequisite_id Prerequisite Element ID
1501
     * @param int    $minScore        Prerequisite min score
1502
     * @param int    $maxScore        Prerequisite max score
1503
     *
1504
     * @return bool True on success, false on error
1505
     */
1506
    public function edit_item_prereq(
1507
        $id,
1508
        $prerequisite_id,
1509
        $minScore = 0,
1510
        $maxScore = 100
1511
    ) {
1512
        if ($this->debug > 0) {
1513
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$minScore.','.$maxScore.')', 0);
1514
        }
1515
1516
        $id = (int) $id;
1517
        $prerequisite_id = (int) $prerequisite_id;
1518
1519
        if (empty($id)) {
1520
            return false;
1521
        }
1522
1523
        if (empty($minScore) || $minScore < 0) {
1524
            $minScore = 0;
1525
        }
1526
1527
        if (empty($maxScore) || $maxScore < 0) {
1528
            $maxScore = 100;
1529
        }
1530
1531
        $minScore = floatval($minScore);
1532
        $maxScore = floatval($maxScore);
1533
1534
        if (empty($prerequisite_id)) {
1535
            $prerequisite_id = 'NULL';
1536
            $minScore = 0;
1537
            $maxScore = 100;
1538
        }
1539
1540
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1541
        $sql = " UPDATE $tbl_lp_item
1542
                 SET
1543
                    prerequisite = $prerequisite_id ,
1544
                    prerequisite_min_score = $minScore ,
1545
                    prerequisite_max_score = $maxScore
1546
                 WHERE iid = $id";
1547
1548
        Database::query($sql);
1549
1550
        return true;
1551
    }
1552
1553
    /**
1554
     * Get the specific prefix index terms of this learning path.
1555
     *
1556
     * @param string $prefix
1557
     *
1558
     * @return array Array of terms
1559
     */
1560
    public function get_common_index_terms_by_prefix($prefix)
1561
    {
1562
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1563
        $terms = get_specific_field_values_list_by_prefix(
1564
            $prefix,
1565
            $this->cc,
1566
            TOOL_LEARNPATH,
1567
            $this->lp_id
1568
        );
1569
        $prefix_terms = [];
1570
        if (!empty($terms)) {
1571
            foreach ($terms as $term) {
1572
                $prefix_terms[] = $term['value'];
1573
            }
1574
        }
1575
1576
        return $prefix_terms;
1577
    }
1578
1579
    /**
1580
     * Gets the number of items currently completed.
1581
     *
1582
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1583
     *
1584
     * @return int The number of items currently completed
1585
     */
1586
    public function get_complete_items_count($failedStatusException = false)
1587
    {
1588
        $i = 0;
1589
        $completedStatusList = [
1590
            'completed',
1591
            'passed',
1592
            'succeeded',
1593
            'browsed',
1594
        ];
1595
1596
        if (!$failedStatusException) {
1597
            $completedStatusList[] = 'failed';
1598
        }
1599
1600
        foreach ($this->items as $id => $dummy) {
1601
            // Trying failed and browsed considered "progressed" as well.
1602
            if ($this->items[$id]->status_is($completedStatusList) &&
1603
                $this->items[$id]->get_type() != 'dir'
1604
            ) {
1605
                $i++;
1606
            }
1607
        }
1608
1609
        return $i;
1610
    }
1611
1612
    /**
1613
     * Gets the current item ID.
1614
     *
1615
     * @return int The current learnpath item id
1616
     */
1617
    public function get_current_item_id()
1618
    {
1619
        $current = 0;
1620
        if (!empty($this->current)) {
1621
            $current = (int) $this->current;
1622
        }
1623
1624
        return $current;
1625
    }
1626
1627
    /**
1628
     * Force to get the first learnpath item id.
1629
     *
1630
     * @return int The current learnpath item id
1631
     */
1632
    public function get_first_item_id()
1633
    {
1634
        $current = 0;
1635
        if (is_array($this->ordered_items)) {
1636
            $current = $this->ordered_items[0];
1637
        }
1638
1639
        return $current;
1640
    }
1641
1642
    /**
1643
     * Gets the total number of items available for viewing in this SCORM.
1644
     *
1645
     * @return int The total number of items
1646
     */
1647
    public function get_total_items_count()
1648
    {
1649
        return count($this->items);
1650
    }
1651
1652
    /**
1653
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1654
     *
1655
     * @return int The total no-chapters number of items
1656
     */
1657
    public function getTotalItemsCountWithoutDirs()
1658
    {
1659
        $total = 0;
1660
        $typeListNotToCount = self::getChapterTypes();
1661
        foreach ($this->items as $temp2) {
1662
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1663
                $total++;
1664
            }
1665
        }
1666
1667
        return $total;
1668
    }
1669
1670
    /**
1671
     *  Sets the first element URL.
1672
     */
1673
    public function first()
1674
    {
1675
        if ($this->debug > 0) {
1676
            error_log('In learnpath::first()', 0);
1677
            error_log('$this->last_item_seen '.$this->last_item_seen);
1678
        }
1679
1680
        // Test if the last_item_seen exists and is not a dir.
1681
        if (count($this->ordered_items) == 0) {
1682
            $this->index = 0;
1683
        }
1684
1685
        if (!empty($this->last_item_seen) &&
1686
            !empty($this->items[$this->last_item_seen]) &&
1687
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1688
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1689
            //&& !$this->items[$this->last_item_seen]->is_done()
1690
        ) {
1691
            if ($this->debug > 2) {
1692
                error_log(
1693
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1694
                    $this->items[$this->last_item_seen]->get_type()
1695
                );
1696
            }
1697
            $index = -1;
1698
            foreach ($this->ordered_items as $myindex => $item_id) {
1699
                if ($item_id == $this->last_item_seen) {
1700
                    $index = $myindex;
1701
                    break;
1702
                }
1703
            }
1704
            if ($index == -1) {
1705
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1706
                if ($this->debug > 2) {
1707
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1708
                }
1709
1710
                return false;
1711
            } else {
1712
                $this->last = $this->last_item_seen;
1713
                $this->current = $this->last_item_seen;
1714
                $this->index = $index;
1715
            }
1716
        } else {
1717
            if ($this->debug > 2) {
1718
                error_log('In learnpath::first() - No last item seen', 0);
1719
            }
1720
            $index = 0;
1721
            // Loop through all ordered items and stop at the first item that is
1722
            // not a directory *and* that has not been completed yet.
1723
            while (!empty($this->ordered_items[$index]) &&
1724
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1725
                (
1726
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1727
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1728
                ) && $index < $this->max_ordered_items) {
1729
                $index++;
1730
            }
1731
1732
            $this->last = $this->current;
1733
            // current is
1734
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1735
            $this->index = $index;
1736
            if ($this->debug > 2) {
1737
                error_log('$index '.$index);
1738
                error_log('In learnpath::first() - No last item seen');
1739
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1740
            }
1741
        }
1742
        if ($this->debug > 2) {
1743
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1744
        }
1745
    }
1746
1747
    /**
1748
     * Gets the information about an item in a format usable as JavaScript to update
1749
     * the JS API by just printing this content into the <head> section of the message frame.
1750
     *
1751
     * @param int $item_id
1752
     *
1753
     * @return string
1754
     */
1755
    public function get_js_info($item_id = 0)
1756
    {
1757
        if ($this->debug > 0) {
1758
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1759
        }
1760
1761
        $info = '';
1762
        $item_id = (int) $item_id;
1763
1764
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1765
            //if item is defined, return values from DB
1766
            $oItem = $this->items[$item_id];
1767
            $info .= '<script language="javascript">';
1768
            $info .= "top.set_score(".$oItem->get_score().");\n";
1769
            $info .= "top.set_max(".$oItem->get_max().");\n";
1770
            $info .= "top.set_min(".$oItem->get_min().");\n";
1771
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1772
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1773
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1774
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1775
            $info .= "top.set_flag_synchronized();";
1776
            $info .= '</script>';
1777
            if ($this->debug > 2) {
1778
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1779
            }
1780
1781
            return $info;
1782
        } else {
1783
            // If item_id is empty, just update to default SCORM data.
1784
            $info .= '<script language="javascript">';
1785
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1786
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1787
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1788
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1789
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1790
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1791
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1792
            $info .= "top.set_flag_synchronized();";
1793
            $info .= '</script>';
1794
            if ($this->debug > 2) {
1795
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1796
            }
1797
1798
            return $info;
1799
        }
1800
    }
1801
1802
    /**
1803
     * Gets the js library from the database.
1804
     *
1805
     * @return string The name of the javascript library to be used
1806
     */
1807
    public function get_js_lib()
1808
    {
1809
        $lib = '';
1810
        if (!empty($this->js_lib)) {
1811
            $lib = $this->js_lib;
1812
        }
1813
1814
        return $lib;
1815
    }
1816
1817
    /**
1818
     * Gets the learnpath database ID.
1819
     *
1820
     * @return int Learnpath ID in the lp table
1821
     */
1822
    public function get_id()
1823
    {
1824
        if (!empty($this->lp_id)) {
1825
            return (int) $this->lp_id;
1826
        }
1827
1828
        return 0;
1829
    }
1830
1831
    /**
1832
     * Gets the last element URL.
1833
     *
1834
     * @return string URL to load into the viewer
1835
     */
1836
    public function get_last()
1837
    {
1838
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1839
        if (count($this->ordered_items) > 0) {
1840
            $this->index = count($this->ordered_items) - 1;
1841
1842
            return $this->ordered_items[$this->index];
1843
        }
1844
1845
        return false;
1846
    }
1847
1848
    /**
1849
     * Get the last element in the first level.
1850
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1851
     *
1852
     * @return mixed
1853
     */
1854
    public function getLastInFirstLevel()
1855
    {
1856
        try {
1857
            $lastId = Database::getManager()
1858
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1859
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1860
                ->setMaxResults(1)
1861
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1862
                ->getSingleScalarResult();
1863
1864
            return $lastId;
1865
        } catch (Exception $exception) {
1866
            return 0;
1867
        }
1868
    }
1869
1870
    /**
1871
     * Gets the navigation bar for the learnpath display screen.
1872
     *
1873
     * @param string $barId
1874
     *
1875
     * @return string The HTML string to use as a navigation bar
1876
     */
1877
    public function get_navigation_bar($barId = '')
1878
    {
1879
        if (empty($barId)) {
1880
            $barId = 'control-top';
1881
        }
1882
        $lpId = $this->lp_id;
1883
        $mycurrentitemid = $this->get_current_item_id();
1884
1885
        $reportingText = get_lang('Reporting');
1886
        $previousText = get_lang('ScormPrevious');
1887
        $nextText = get_lang('ScormNext');
1888
        $fullScreenText = get_lang('ScormExitFullScreen');
1889
1890
        $settings = api_get_configuration_value('lp_view_settings');
1891
        $display = isset($settings['display']) ? $settings['display'] : false;
1892
        $reportingIcon = '
1893
            <a class="icon-toolbar" 
1894
                id="stats_link"
1895
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
1896
                onclick="window.parent.API.save_asset(); return true;" 
1897
                target="content_name" title="'.$reportingText.'">
1898
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1899
            </a>';
1900
1901
        if (!empty($display)) {
1902
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1903
            if ($showReporting === false) {
1904
                $reportingIcon = '';
1905
            }
1906
        }
1907
1908
        $hideArrows = false;
1909
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1910
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1911
        }
1912
1913
        $previousIcon = '';
1914
        $nextIcon = '';
1915
        if ($hideArrows === false) {
1916
            $previousIcon = '
1917
                <a class="icon-toolbar" id="scorm-previous" href="#" 
1918
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1919
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1920
                </a>';
1921
1922
            $nextIcon = '
1923
                <a class="icon-toolbar" id="scorm-next" href="#" 
1924
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1925
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1926
                </a>';
1927
        }
1928
1929
        if ($this->mode === 'fullscreen') {
1930
            $navbar = '
1931
                  <span id="'.$barId.'" class="buttons">
1932
                    '.$reportingIcon.'
1933
                    '.$previousIcon.'                    
1934
                    '.$nextIcon.'
1935
                    <a class="icon-toolbar" id="view-embedded" 
1936
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1937
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1938
                    </a>
1939
                  </span>';
1940
        } else {
1941
            $navbar = '
1942
                 <span id="'.$barId.'" class="buttons text-right">
1943
                    '.$reportingIcon.'
1944
                    '.$previousIcon.'
1945
                    '.$nextIcon.'               
1946
                </span>';
1947
        }
1948
1949
        return $navbar;
1950
    }
1951
1952
    /**
1953
     * Gets the next resource in queue (url).
1954
     *
1955
     * @return string URL to load into the viewer
1956
     */
1957
    public function get_next_index()
1958
    {
1959
        if ($this->debug > 0) {
1960
            error_log('In learnpath::get_next_index()', 0);
1961
        }
1962
        // TODO
1963
        $index = $this->index;
1964
        $index++;
1965
        if ($this->debug > 2) {
1966
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
1967
        }
1968
        while (
1969
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1970
            $index < $this->max_ordered_items
1971
        ) {
1972
            $index++;
1973
            if ($index == $this->max_ordered_items) {
1974
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1975
                    return $this->index;
1976
                } else {
1977
                    return $index;
1978
                }
1979
            }
1980
        }
1981
        if (empty($this->ordered_items[$index])) {
1982
            return $this->index;
1983
        }
1984
        if ($this->debug > 2) {
1985
            error_log('index is now '.$index, 0);
1986
        }
1987
1988
        return $index;
1989
    }
1990
1991
    /**
1992
     * Gets item_id for the next element.
1993
     *
1994
     * @return int Next item (DB) ID
1995
     */
1996
    public function get_next_item_id()
1997
    {
1998
        if ($this->debug > 0) {
1999
            error_log('In learnpath::get_next_item_id()', 0);
2000
        }
2001
        $new_index = $this->get_next_index();
2002
        if (!empty($new_index)) {
2003
            if (isset($this->ordered_items[$new_index])) {
2004
                if ($this->debug > 2) {
2005
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2006
                }
2007
2008
                return $this->ordered_items[$new_index];
2009
            }
2010
        }
2011
        if ($this->debug > 2) {
2012
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2013
        }
2014
2015
        return 0;
2016
    }
2017
2018
    /**
2019
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2020
     *
2021
     * Generally, the package provided is in the form of a zip file, so the function
2022
     * has been written to test a zip file. If not a zip, the function will return the
2023
     * default return value: ''
2024
     *
2025
     * @param string $file_path the path to the file
2026
     * @param string $file_name the original name of the file
2027
     *
2028
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2029
     */
2030
    public static function get_package_type($file_path, $file_name)
2031
    {
2032
        // Get name of the zip file without the extension.
2033
        $file_info = pathinfo($file_name);
2034
        $extension = $file_info['extension']; // Extension only.
2035
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2036
                'dll',
2037
                'exe',
2038
            ])) {
2039
            return 'oogie';
2040
        }
2041
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2042
                'dll',
2043
                'exe',
2044
            ])) {
2045
            return 'woogie';
2046
        }
2047
2048
        $zipFile = new PclZip($file_path);
2049
        // Check the zip content (real size and file extension).
2050
        $zipContentArray = $zipFile->listContent();
2051
        $package_type = '';
2052
        $manifest = '';
2053
        $aicc_match_crs = 0;
2054
        $aicc_match_au = 0;
2055
        $aicc_match_des = 0;
2056
        $aicc_match_cst = 0;
2057
2058
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2059
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2060
            foreach ($zipContentArray as $thisContent) {
2061
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2062
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2063
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2064
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2065
                    $package_type = 'scorm';
2066
                    break; // Exit the foreach loop.
2067
                } elseif (
2068
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2069
                    in_array(
2070
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2071
                        ['crs', 'au', 'des', 'cst']
2072
                    )
2073
                ) {
2074
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2075
                    switch ($ext) {
2076
                        case 'crs':
2077
                            $aicc_match_crs = 1;
2078
                            break;
2079
                        case 'au':
2080
                            $aicc_match_au = 1;
2081
                            break;
2082
                        case 'des':
2083
                            $aicc_match_des = 1;
2084
                            break;
2085
                        case 'cst':
2086
                            $aicc_match_cst = 1;
2087
                            break;
2088
                        default:
2089
                            break;
2090
                    }
2091
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2092
                } else {
2093
                    $package_type = '';
2094
                }
2095
            }
2096
        }
2097
2098
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2099
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2100
            $package_type = 'aicc';
2101
        }
2102
2103
        // Try with chamilo course builder
2104
        if (empty($package_type)) {
2105
            $package_type = 'chamilo';
2106
        }
2107
2108
        return $package_type;
2109
    }
2110
2111
    /**
2112
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2113
     *
2114
     * @return string URL to load into the viewer
2115
     */
2116
    public function get_previous_index()
2117
    {
2118
        $index = $this->index;
2119
        if (isset($this->ordered_items[$index - 1])) {
2120
            $index--;
2121
            while (isset($this->ordered_items[$index]) &&
2122
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2123
            ) {
2124
                $index--;
2125
                if ($index < 0) {
2126
                    return $this->index;
2127
                }
2128
            }
2129
        }
2130
2131
        return $index;
2132
    }
2133
2134
    /**
2135
     * Gets item_id for the next element.
2136
     *
2137
     * @return int Previous item (DB) ID
2138
     */
2139
    public function get_previous_item_id()
2140
    {
2141
        $index = $this->get_previous_index();
2142
2143
        return $this->ordered_items[$index];
2144
    }
2145
2146
    /**
2147
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2148
     *
2149
     * @param int    $lpItemId
2150
     * @param string $autostart
2151
     *
2152
     * @return string The mediaplayer HTML
2153
     */
2154
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2155
    {
2156
        $course_id = api_get_course_int_id();
2157
        $_course = api_get_course_info();
2158
        if (empty($_course)) {
2159
            return '';
2160
        }
2161
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2162
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2163
        $lpItemId = (int) $lpItemId;
2164
2165
        /** @var learnpathItem $item */
2166
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2167
        $itemViewId = 0;
2168
        if ($item) {
2169
            $itemViewId = (int) $item->db_item_view_id;
2170
        }
2171
2172
        // Getting all the information about the item.
2173
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status 
2174
                FROM $tbl_lp_item as lpi
2175
                INNER JOIN $tbl_lp_item_view as lp_view
2176
                ON (lpi.iid = lp_view.lp_item_id)
2177
                WHERE
2178
                    lp_view.iid = $itemViewId AND
2179
                    lpi.iid = $lpItemId AND
2180
                    lp_view.c_id = $course_id";
2181
        $result = Database::query($sql);
2182
        $row = Database::fetch_assoc($result);
2183
        $output = '';
2184
2185
        if (!empty($row['audio'])) {
2186
            $list = $_SESSION['oLP']->get_toc();
2187
2188
            switch ($row['item_type']) {
2189
                case 'quiz':
2190
                    $type_quiz = false;
2191
                    foreach ($list as $toc) {
2192
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2193
                            $type_quiz = true;
2194
                        }
2195
                    }
2196
2197
                    if ($type_quiz) {
2198
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2199
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2200
                        } else {
2201
                            $autostart_audio = $autostart;
2202
                        }
2203
                    }
2204
                    break;
2205
                case TOOL_READOUT_TEXT:;
2206
                    $autostart_audio = 'false';
2207
                    break;
2208
                default:
2209
                    $autostart_audio = 'true';
2210
            }
2211
2212
            $courseInfo = api_get_course_info();
2213
            $audio = $row['audio'];
2214
2215
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2216
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2217
2218
            if (!file_exists($file)) {
2219
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2220
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2221
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2222
            }
2223
2224
            $player = Display::getMediaPlayer(
2225
                $file,
2226
                [
2227
                    'id' => 'lp_audio_media_player',
2228
                    'url' => $url,
2229
                    'autoplay' => $autostart_audio,
2230
                    'width' => '100%',
2231
                ]
2232
            );
2233
2234
            // The mp3 player.
2235
            $output = '<div id="container">';
2236
            $output .= $player;
2237
            $output .= '</div>';
2238
        }
2239
2240
        return $output;
2241
    }
2242
2243
    /**
2244
     * @param int   $studentId
2245
     * @param int   $prerequisite
2246
     * @param array $courseInfo
2247
     * @param int   $sessionId
2248
     *
2249
     * @return bool
2250
     */
2251
    public static function isBlockedByPrerequisite(
2252
        $studentId,
2253
        $prerequisite,
2254
        $courseInfo,
2255
        $sessionId
2256
    ) {
2257
        if (empty($courseInfo)) {
2258
            return false;
2259
        }
2260
2261
        $courseId = $courseInfo['real_id'];
2262
2263
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2264
        if ($allow) {
2265
            if (api_is_allowed_to_edit() ||
2266
                api_is_platform_admin(true) ||
2267
                api_is_drh() ||
2268
                api_is_coach($sessionId, $courseId, false)
2269
            ) {
2270
                return false;
2271
            }
2272
        }
2273
2274
        $isBlocked = false;
2275
        if (!empty($prerequisite)) {
2276
            $progress = self::getProgress(
2277
                $prerequisite,
2278
                $studentId,
2279
                $courseId,
2280
                $sessionId
2281
            );
2282
            if ($progress < 100) {
2283
                $isBlocked = true;
2284
            }
2285
2286
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2287
                // Block if it does not exceed minimum time
2288
                // Minimum time (in minutes) to pass the learning path
2289
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2290
2291
                if ($accumulateWorkTime > 0) {
2292
                    // Total time in course (sum of times in learning paths from course)
2293
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2294
2295
                    // Connect with the plugin_licences_course_session table
2296
                    // which indicates what percentage of the time applies
2297
                    // Minimum connection percentage
2298
                    $perc = 100;
2299
                    // Time from the course
2300
                    $tc = $accumulateWorkTimeTotal;
2301
2302
                    // Percentage of the learning paths
2303
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2304
                    // Minimum time for each learning path
2305
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2306
2307
                    // Spent time (in seconds) so far in the learning path
2308
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2309
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2310
2311
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2312
                        $isBlocked = true;
2313
                    }
2314
                }
2315
            }
2316
        }
2317
2318
        return $isBlocked;
2319
    }
2320
2321
    /**
2322
     * Checks if the learning path is visible for student after the progress
2323
     * of its prerequisite is completed, considering the time availability and
2324
     * the LP visibility.
2325
     *
2326
     * @param int   $lp_id
2327
     * @param int   $student_id
2328
     * @param array $courseInfo
2329
     * @param int   $sessionId
2330
     *
2331
     * @return bool
2332
     */
2333
    public static function is_lp_visible_for_student(
2334
        $lp_id,
2335
        $student_id,
2336
        $courseInfo = [],
2337
        $sessionId = 0
2338
    ) {
2339
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2340
        $lp_id = (int) $lp_id;
2341
        $sessionId = (int) $sessionId;
2342
2343
        if (empty($courseInfo)) {
2344
            return false;
2345
        }
2346
2347
        if (empty($sessionId)) {
2348
            $sessionId = api_get_session_id();
2349
        }
2350
2351
        $courseId = $courseInfo['real_id'];
2352
2353
        $itemInfo = api_get_item_property_info(
2354
            $courseId,
2355
            TOOL_LEARNPATH,
2356
            $lp_id,
2357
            $sessionId
2358
        );
2359
2360
        // If the item was deleted.
2361
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2362
            return false;
2363
        }
2364
2365
        // @todo remove this query and load the row info as a parameter
2366
        $table = Database::get_course_table(TABLE_LP_MAIN);
2367
        // Get current prerequisite
2368
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2369
                FROM $table
2370
                WHERE iid = $lp_id";
2371
        $rs = Database::query($sql);
2372
        $now = time();
2373
        if (Database::num_rows($rs) > 0) {
2374
            $row = Database::fetch_array($rs, 'ASSOC');
2375
            $prerequisite = $row['prerequisite'];
2376
            $is_visible = true;
2377
2378
            $isBlocked = self::isBlockedByPrerequisite(
2379
                $student_id,
2380
                $prerequisite,
2381
                $courseInfo,
2382
                $sessionId
2383
            );
2384
2385
            if ($isBlocked) {
2386
                $is_visible = false;
2387
            }
2388
2389
            // Also check the time availability of the LP
2390
            if ($is_visible) {
2391
                // Adding visibility restrictions
2392
                if (!empty($row['publicated_on'])) {
2393
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2394
                        $is_visible = false;
2395
                    }
2396
                }
2397
                // Blocking empty start times see BT#2800
2398
                global $_custom;
2399
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2400
                    $_custom['lps_hidden_when_no_start_date']
2401
                ) {
2402
                    if (empty($row['publicated_on'])) {
2403
                        $is_visible = false;
2404
                    }
2405
                }
2406
2407
                if (!empty($row['expired_on'])) {
2408
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2409
                        $is_visible = false;
2410
                    }
2411
                }
2412
            }
2413
2414
            if ($is_visible) {
2415
                $subscriptionSettings = self::getSubscriptionSettings();
2416
2417
                // Check if the subscription users/group to a LP is ON
2418
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2419
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2420
                ) {
2421
                    // Try group
2422
                    $is_visible = false;
2423
                    // Checking only the user visibility
2424
                    $userVisibility = api_get_item_visibility(
2425
                        $courseInfo,
2426
                        'learnpath',
2427
                        $row['id'],
2428
                        $sessionId,
2429
                        $student_id,
2430
                        'LearnpathSubscription'
2431
                    );
2432
2433
                    if ($userVisibility == 1) {
2434
                        $is_visible = true;
2435
                    } else {
2436
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2437
                        if (!empty($userGroups)) {
2438
                            foreach ($userGroups as $groupInfo) {
2439
                                $groupId = $groupInfo['iid'];
2440
                                $userVisibility = api_get_item_visibility(
2441
                                    $courseInfo,
2442
                                    'learnpath',
2443
                                    $row['id'],
2444
                                    $sessionId,
2445
                                    null,
2446
                                    'LearnpathSubscription',
2447
                                    $groupId
2448
                                );
2449
2450
                                if ($userVisibility == 1) {
2451
                                    $is_visible = true;
2452
                                    break;
2453
                                }
2454
                            }
2455
                        }
2456
                    }
2457
                }
2458
            }
2459
2460
            return $is_visible;
2461
        }
2462
2463
        return false;
2464
    }
2465
2466
    /**
2467
     * @param int $lpId
2468
     * @param int $userId
2469
     * @param int $courseId
2470
     * @param int $sessionId
2471
     *
2472
     * @return int
2473
     */
2474
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2475
    {
2476
        $lpId = (int) $lpId;
2477
        $userId = (int) $userId;
2478
        $courseId = (int) $courseId;
2479
        $sessionId = (int) $sessionId;
2480
2481
        $sessionCondition = api_get_session_condition($sessionId);
2482
        $table = Database::get_course_table(TABLE_LP_VIEW);
2483
        $sql = "SELECT progress FROM $table
2484
                WHERE
2485
                    c_id = $courseId AND
2486
                    lp_id = $lpId AND
2487
                    user_id = $userId $sessionCondition ";
2488
        $res = Database::query($sql);
2489
2490
        $progress = 0;
2491
        if (Database::num_rows($res) > 0) {
2492
            $row = Database::fetch_array($res);
2493
            $progress = (int) $row['progress'];
2494
        }
2495
2496
        return $progress;
2497
    }
2498
2499
    /**
2500
     * @param array $lpList
2501
     * @param int   $userId
2502
     * @param int   $courseId
2503
     * @param int   $sessionId
2504
     *
2505
     * @return array
2506
     */
2507
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2508
    {
2509
        $lpList = array_map('intval', $lpList);
2510
        if (empty($lpList)) {
2511
            return [];
2512
        }
2513
2514
        $lpList = implode("','", $lpList);
2515
2516
        $userId = (int) $userId;
2517
        $courseId = (int) $courseId;
2518
        $sessionId = (int) $sessionId;
2519
2520
        $sessionCondition = api_get_session_condition($sessionId);
2521
        $table = Database::get_course_table(TABLE_LP_VIEW);
2522
        $sql = "SELECT lp_id, progress FROM $table
2523
                WHERE
2524
                    c_id = $courseId AND
2525
                    lp_id IN ('".$lpList."') AND
2526
                    user_id = $userId $sessionCondition ";
2527
        $res = Database::query($sql);
2528
2529
        if (Database::num_rows($res) > 0) {
2530
            $list = [];
2531
            while ($row = Database::fetch_array($res)) {
2532
                $list[$row['lp_id']] = $row['progress'];
2533
            }
2534
2535
            return $list;
2536
        }
2537
2538
        return [];
2539
    }
2540
2541
    /**
2542
     * Displays a progress bar
2543
     * completed so far.
2544
     *
2545
     * @param int    $percentage Progress value to display
2546
     * @param string $text_add   Text to display near the progress value
2547
     *
2548
     * @return string HTML string containing the progress bar
2549
     */
2550
    public static function get_progress_bar($percentage = -1, $text_add = '')
2551
    {
2552
        $text = $percentage.$text_add;
2553
        $output = '<div class="progress">
2554
            <div id="progress_bar_value" 
2555
                class="progress-bar progress-bar-success" role="progressbar" 
2556
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2557
            '.$text.'
2558
            </div>
2559
        </div>';
2560
2561
        return $output;
2562
    }
2563
2564
    /**
2565
     * @param string $mode can be '%' or 'abs'
2566
     *                     otherwise this value will be used $this->progress_bar_mode
2567
     *
2568
     * @return string
2569
     */
2570
    public function getProgressBar($mode = null)
2571
    {
2572
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2573
2574
        return self::get_progress_bar($percentage, $text_add);
2575
    }
2576
2577
    /**
2578
     * Gets the progress bar info to display inside the progress bar.
2579
     * Also used by scorm_api.php.
2580
     *
2581
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2582
     *                     we display a number of completed elements per total elements
2583
     * @param int    $add  Additional steps to fake as completed
2584
     *
2585
     * @return array Percentage or number and symbol (% or /xx)
2586
     */
2587
    public function get_progress_bar_text($mode = '', $add = 0)
2588
    {
2589
        if ($this->debug > 0) {
2590
            error_log('In learnpath::get_progress_bar_text()', 0);
2591
        }
2592
        if (empty($mode)) {
2593
            $mode = $this->progress_bar_mode;
2594
        }
2595
        $total_items = $this->getTotalItemsCountWithoutDirs();
2596
        if ($this->debug > 2) {
2597
            error_log('Total items available in this learnpath: '.$total_items, 0);
2598
        }
2599
        $completeItems = $this->get_complete_items_count();
2600
        if ($this->debug > 2) {
2601
            error_log('Items completed so far: '.$completeItems, 0);
2602
        }
2603
        if ($add != 0) {
2604
            $completeItems += $add;
2605
            if ($this->debug > 2) {
2606
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2607
            }
2608
        }
2609
        $text = '';
2610
        if ($completeItems > $total_items) {
2611
            $completeItems = $total_items;
2612
        }
2613
        $percentage = 0;
2614
        if ($mode == '%') {
2615
            if ($total_items > 0) {
2616
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2617
            } else {
2618
                $percentage = 0;
2619
            }
2620
            $percentage = number_format($percentage, 0);
2621
            $text = '%';
2622
        } elseif ($mode == 'abs') {
2623
            $percentage = $completeItems;
2624
            $text = '/'.$total_items;
2625
        }
2626
2627
        return [
2628
            $percentage,
2629
            $text,
2630
        ];
2631
    }
2632
2633
    /**
2634
     * Gets the progress bar mode.
2635
     *
2636
     * @return string The progress bar mode attribute
2637
     */
2638
    public function get_progress_bar_mode()
2639
    {
2640
        if (!empty($this->progress_bar_mode)) {
2641
            return $this->progress_bar_mode;
2642
        }
2643
2644
        return '%';
2645
    }
2646
2647
    /**
2648
     * Gets the learnpath theme (remote or local).
2649
     *
2650
     * @return string Learnpath theme
2651
     */
2652
    public function get_theme()
2653
    {
2654
        if (!empty($this->theme)) {
2655
            return $this->theme;
2656
        }
2657
2658
        return '';
2659
    }
2660
2661
    /**
2662
     * Gets the learnpath session id.
2663
     *
2664
     * @return int
2665
     */
2666
    public function get_lp_session_id()
2667
    {
2668
        if (!empty($this->lp_session_id)) {
2669
            return (int) $this->lp_session_id;
2670
        }
2671
2672
        return 0;
2673
    }
2674
2675
    /**
2676
     * Gets the learnpath image.
2677
     *
2678
     * @return string Web URL of the LP image
2679
     */
2680
    public function get_preview_image()
2681
    {
2682
        if (!empty($this->preview_image)) {
2683
            return $this->preview_image;
2684
        }
2685
2686
        return '';
2687
    }
2688
2689
    /**
2690
     * @param string $size
2691
     * @param string $path_type
2692
     *
2693
     * @return bool|string
2694
     */
2695
    public function get_preview_image_path($size = null, $path_type = 'web')
2696
    {
2697
        $preview_image = $this->get_preview_image();
2698
        if (isset($preview_image) && !empty($preview_image)) {
2699
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2700
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2701
2702
            if (isset($size)) {
2703
                $info = pathinfo($preview_image);
2704
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2705
2706
                if (file_exists($image_sys_path.$image_custom_size)) {
2707
                    if ($path_type == 'web') {
2708
                        return $image_path.$image_custom_size;
2709
                    } else {
2710
                        return $image_sys_path.$image_custom_size;
2711
                    }
2712
                }
2713
            } else {
2714
                if ($path_type == 'web') {
2715
                    return $image_path.$preview_image;
2716
                } else {
2717
                    return $image_sys_path.$preview_image;
2718
                }
2719
            }
2720
        }
2721
2722
        return false;
2723
    }
2724
2725
    /**
2726
     * Gets the learnpath author.
2727
     *
2728
     * @return string LP's author
2729
     */
2730
    public function get_author()
2731
    {
2732
        if (!empty($this->author)) {
2733
            return $this->author;
2734
        }
2735
2736
        return '';
2737
    }
2738
2739
    /**
2740
     * Gets hide table of contents.
2741
     *
2742
     * @return int
2743
     */
2744
    public function getHideTableOfContents()
2745
    {
2746
        return (int) $this->hide_toc_frame;
2747
    }
2748
2749
    /**
2750
     * Generate a new prerequisites string for a given item. If this item was a sco and
2751
     * its prerequisites were strings (instead of IDs), then transform those strings into
2752
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2753
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2754
     * same rule as the scormExport() method.
2755
     *
2756
     * @param int $item_id Item ID
2757
     *
2758
     * @return string Prerequisites string ready for the export as SCORM
2759
     */
2760
    public function get_scorm_prereq_string($item_id)
2761
    {
2762
        if ($this->debug > 0) {
2763
            error_log('In learnpath::get_scorm_prereq_string()');
2764
        }
2765
        if (!is_object($this->items[$item_id])) {
2766
            return false;
2767
        }
2768
        /** @var learnpathItem $oItem */
2769
        $oItem = $this->items[$item_id];
2770
        $prereq = $oItem->get_prereq_string();
2771
2772
        if (empty($prereq)) {
2773
            return '';
2774
        }
2775
        if (preg_match('/^\d+$/', $prereq) &&
2776
            isset($this->items[$prereq]) &&
2777
            is_object($this->items[$prereq])
2778
        ) {
2779
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2780
            // then simply return it (with the ITEM_ prefix).
2781
            //return 'ITEM_' . $prereq;
2782
            return $this->items[$prereq]->ref;
2783
        } else {
2784
            if (isset($this->refs_list[$prereq])) {
2785
                // It's a simple string item from which the ID can be found in the refs list,
2786
                // so we can transform it directly to an ID for export.
2787
                return $this->items[$this->refs_list[$prereq]]->ref;
2788
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2789
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2790
            } else {
2791
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2792
                // and replace them, one by one, by the internal IDs (chamilo db)
2793
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2794
                // by a space as well.
2795
                $find = [
2796
                    '&',
2797
                    '|',
2798
                    '~',
2799
                    '=',
2800
                    '<>',
2801
                    '{',
2802
                    '}',
2803
                    '*',
2804
                    '(',
2805
                    ')',
2806
                ];
2807
                $replace = [
2808
                    ' ',
2809
                    ' ',
2810
                    ' ',
2811
                    ' ',
2812
                    ' ',
2813
                    ' ',
2814
                    ' ',
2815
                    ' ',
2816
                    ' ',
2817
                    ' ',
2818
                ];
2819
                $prereq_mod = str_replace($find, $replace, $prereq);
2820
                $ids = explode(' ', $prereq_mod);
2821
                foreach ($ids as $id) {
2822
                    $id = trim($id);
2823
                    if (isset($this->refs_list[$id])) {
2824
                        $prereq = preg_replace(
2825
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2826
                            'ITEM_'.$this->refs_list[$id],
2827
                            $prereq
2828
                        );
2829
                    }
2830
                }
2831
2832
                return $prereq;
2833
            }
2834
        }
2835
    }
2836
2837
    /**
2838
     * Returns the XML DOM document's node.
2839
     *
2840
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2841
     * @param string   $id       The identifier to look for
2842
     *
2843
     * @return mixed The reference to the element found with that identifier. False if not found
2844
     */
2845
    public function get_scorm_xml_node(&$children, $id)
2846
    {
2847
        for ($i = 0; $i < $children->length; $i++) {
2848
            $item_temp = $children->item($i);
2849
            if ($item_temp->nodeName == 'item') {
2850
                if ($item_temp->getAttribute('identifier') == $id) {
2851
                    return $item_temp;
2852
                }
2853
            }
2854
            $subchildren = $item_temp->childNodes;
2855
            if ($subchildren && $subchildren->length > 0) {
2856
                $val = $this->get_scorm_xml_node($subchildren, $id);
2857
                if (is_object($val)) {
2858
                    return $val;
2859
                }
2860
            }
2861
        }
2862
2863
        return false;
2864
    }
2865
2866
    /**
2867
     * Gets the status list for all LP's items.
2868
     *
2869
     * @return array Array of [index] => [item ID => current status]
2870
     */
2871
    public function get_items_status_list()
2872
    {
2873
        $list = [];
2874
        foreach ($this->ordered_items as $item_id) {
2875
            $list[] = [
2876
                $item_id => $this->items[$item_id]->get_status(),
2877
            ];
2878
        }
2879
2880
        return $list;
2881
    }
2882
2883
    /**
2884
     * Return the number of interactions for the given learnpath Item View ID.
2885
     * This method can be used as static.
2886
     *
2887
     * @param int $lp_iv_id  Item View ID
2888
     * @param int $course_id course id
2889
     *
2890
     * @return int
2891
     */
2892
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2893
    {
2894
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2895
        $lp_iv_id = (int) $lp_iv_id;
2896
        $course_id = (int) $course_id;
2897
2898
        $sql = "SELECT count(*) FROM $table
2899
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2900
        $res = Database::query($sql);
2901
        $num = 0;
2902
        if (Database::num_rows($res)) {
2903
            $row = Database::fetch_array($res);
2904
            $num = $row[0];
2905
        }
2906
2907
        return $num;
2908
    }
2909
2910
    /**
2911
     * Return the interactions as an array for the given lp_iv_id.
2912
     * This method can be used as static.
2913
     *
2914
     * @param int $lp_iv_id Learnpath Item View ID
2915
     *
2916
     * @return array
2917
     *
2918
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2919
     */
2920
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2921
    {
2922
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2923
        $list = [];
2924
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2925
        $lp_iv_id = (int) $lp_iv_id;
2926
2927
        if (empty($lp_iv_id) || empty($course_id)) {
2928
            return [];
2929
        }
2930
2931
        $sql = "SELECT * FROM $table
2932
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2933
                ORDER BY order_id ASC";
2934
        $res = Database::query($sql);
2935
        $num = Database::num_rows($res);
2936
        if ($num > 0) {
2937
            $list[] = [
2938
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2939
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2940
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2941
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2942
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2943
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2944
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2945
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2946
                'student_response_formatted' => '',
2947
            ];
2948
            while ($row = Database::fetch_array($res)) {
2949
                $studentResponseFormatted = urldecode($row['student_response']);
2950
                $content_student_response = explode('__|', $studentResponseFormatted);
2951
                if (count($content_student_response) > 0) {
2952
                    if (count($content_student_response) >= 3) {
2953
                        // Pop the element off the end of array.
2954
                        array_pop($content_student_response);
2955
                    }
2956
                    $studentResponseFormatted = implode(',', $content_student_response);
2957
                }
2958
2959
                $list[] = [
2960
                    'order_id' => $row['order_id'] + 1,
2961
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2962
                    'type' => $row['interaction_type'],
2963
                    'time' => $row['completion_time'],
2964
                    'correct_responses' => '', // Hide correct responses from students.
2965
                    'student_response' => $row['student_response'],
2966
                    'result' => $row['result'],
2967
                    'latency' => $row['latency'],
2968
                    'student_response_formatted' => $studentResponseFormatted,
2969
                ];
2970
            }
2971
        }
2972
2973
        return $list;
2974
    }
2975
2976
    /**
2977
     * Return the number of objectives for the given learnpath Item View ID.
2978
     * This method can be used as static.
2979
     *
2980
     * @param int $lp_iv_id  Item View ID
2981
     * @param int $course_id Course ID
2982
     *
2983
     * @return int Number of objectives
2984
     */
2985
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2986
    {
2987
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2988
        $course_id = (int) $course_id;
2989
        $lp_iv_id = (int) $lp_iv_id;
2990
        $sql = "SELECT count(*) FROM $table
2991
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2992
        //@todo seems that this always returns 0
2993
        $res = Database::query($sql);
2994
        $num = 0;
2995
        if (Database::num_rows($res)) {
2996
            $row = Database::fetch_array($res);
2997
            $num = $row[0];
2998
        }
2999
3000
        return $num;
3001
    }
3002
3003
    /**
3004
     * Return the objectives as an array for the given lp_iv_id.
3005
     * This method can be used as static.
3006
     *
3007
     * @param int $lpItemViewId Learnpath Item View ID
3008
     * @param int $course_id
3009
     *
3010
     * @return array
3011
     *
3012
     * @todo    Translate labels
3013
     */
3014
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3015
    {
3016
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3017
        $lpItemViewId = (int) $lpItemViewId;
3018
3019
        if (empty($course_id) || empty($lpItemViewId)) {
3020
            return [];
3021
        }
3022
3023
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3024
        $sql = "SELECT * FROM $table
3025
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3026
                ORDER BY order_id ASC";
3027
        $res = Database::query($sql);
3028
        $num = Database::num_rows($res);
3029
        $list = [];
3030
        if ($num > 0) {
3031
            $list[] = [
3032
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3033
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3034
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3035
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3036
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3037
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3038
            ];
3039
            while ($row = Database::fetch_array($res)) {
3040
                $list[] = [
3041
                    'order_id' => $row['order_id'] + 1,
3042
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3043
                    'score_raw' => $row['score_raw'],
3044
                    'score_max' => $row['score_max'],
3045
                    'score_min' => $row['score_min'],
3046
                    'status' => $row['status'],
3047
                ];
3048
            }
3049
        }
3050
3051
        return $list;
3052
    }
3053
3054
    /**
3055
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3056
     * used by get_html_toc() to be ready to display.
3057
     *
3058
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3059
     */
3060
    public function get_toc()
3061
    {
3062
        $toc = [];
3063
        foreach ($this->ordered_items as $item_id) {
3064
            if ($this->debug > 2) {
3065
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3066
            }
3067
            // TODO: Change this link generation and use new function instead.
3068
            $toc[] = [
3069
                'id' => $item_id,
3070
                'title' => $this->items[$item_id]->get_title(),
3071
                'status' => $this->items[$item_id]->get_status(),
3072
                'level' => $this->items[$item_id]->get_level(),
3073
                'type' => $this->items[$item_id]->get_type(),
3074
                'description' => $this->items[$item_id]->get_description(),
3075
                'path' => $this->items[$item_id]->get_path(),
3076
                'parent' => $this->items[$item_id]->get_parent(),
3077
            ];
3078
        }
3079
        if ($this->debug > 2) {
3080
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3081
        }
3082
3083
        return $toc;
3084
    }
3085
3086
    /**
3087
     * Generate and return the table of contents for this learnpath. The JS
3088
     * table returned is used inside of scorm_api.php.
3089
     *
3090
     * @param string $varname
3091
     *
3092
     * @return string A JS array variable construction
3093
     */
3094
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3095
    {
3096
        $toc = $varname.' = new Array();';
3097
        foreach ($this->ordered_items as $item_id) {
3098
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3099
        }
3100
3101
        return $toc;
3102
    }
3103
3104
    /**
3105
     * Gets the learning path type.
3106
     *
3107
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3108
     *
3109
     * @return mixed Type ID or name, depending on the parameter
3110
     */
3111
    public function get_type($get_name = false)
3112
    {
3113
        $res = false;
3114
        if (!empty($this->type) && (!$get_name)) {
3115
            $res = $this->type;
3116
        }
3117
3118
        return $res;
3119
    }
3120
3121
    /**
3122
     * Gets the learning path type as static method.
3123
     *
3124
     * @param int $lp_id
3125
     *
3126
     * @return mixed Type ID or name, depending on the parameter
3127
     */
3128
    public static function get_type_static($lp_id = 0)
3129
    {
3130
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3131
        $lp_id = (int) $lp_id;
3132
        $sql = "SELECT lp_type FROM $tbl_lp
3133
                WHERE iid = $lp_id";
3134
        $res = Database::query($sql);
3135
        if ($res === false) {
3136
            return null;
3137
        }
3138
        if (Database::num_rows($res) <= 0) {
3139
            return null;
3140
        }
3141
        $row = Database::fetch_array($res);
3142
3143
        return $row['lp_type'];
3144
    }
3145
3146
    /**
3147
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3148
     * This method can be used as abstract and is recursive.
3149
     *
3150
     * @param int $lp        Learnpath ID
3151
     * @param int $parent    Parent ID of the items to look for
3152
     * @param int $course_id
3153
     *
3154
     * @return array Ordered list of item IDs (empty array on error)
3155
     */
3156
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3157
    {
3158
        if (empty($course_id)) {
3159
            $course_id = api_get_course_int_id();
3160
        } else {
3161
            $course_id = (int) $course_id;
3162
        }
3163
        $list = [];
3164
3165
        if (empty($lp)) {
3166
            return $list;
3167
        }
3168
3169
        $lp = (int) $lp;
3170
        $parent = (int) $parent;
3171
3172
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3173
        $sql = "SELECT iid FROM $tbl_lp_item
3174
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3175
                ORDER BY display_order";
3176
3177
        $res = Database::query($sql);
3178
        while ($row = Database::fetch_array($res)) {
3179
            $sublist = self::get_flat_ordered_items_list(
3180
                $lp,
3181
                $row['iid'],
3182
                $course_id
3183
            );
3184
            $list[] = $row['iid'];
3185
            foreach ($sublist as $item) {
3186
                $list[] = $item;
3187
            }
3188
        }
3189
3190
        return $list;
3191
    }
3192
3193
    /**
3194
     * @return array
3195
     */
3196
    public static function getChapterTypes()
3197
    {
3198
        return [
3199
            'dir',
3200
        ];
3201
    }
3202
3203
    /**
3204
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3205
     *
3206
     * @param $tree
3207
     *
3208
     * @return array HTML TOC ready to display
3209
     */
3210
    public function getParentToc($tree)
3211
    {
3212
        if (empty($tree)) {
3213
            $tree = $this->get_toc();
3214
        }
3215
        $dirTypes = self::getChapterTypes();
3216
        $myCurrentId = $this->get_current_item_id();
3217
        $listParent = [];
3218
        $listChildren = [];
3219
        $listNotParent = [];
3220
        $list = [];
3221
        foreach ($tree as $subtree) {
3222
            if (in_array($subtree['type'], $dirTypes)) {
3223
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3224
                $subtree['children'] = $listChildren;
3225
                if (!empty($subtree['children'])) {
3226
                    foreach ($subtree['children'] as $subItem) {
3227
                        if ($subItem['id'] == $this->current) {
3228
                            $subtree['parent_current'] = 'in';
3229
                            $subtree['current'] = 'on';
3230
                        }
3231
                    }
3232
                }
3233
                $listParent[] = $subtree;
3234
            }
3235
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3236
                $classStatus = [
3237
                    'not attempted' => 'scorm_not_attempted',
3238
                    'incomplete' => 'scorm_not_attempted',
3239
                    'failed' => 'scorm_failed',
3240
                    'completed' => 'scorm_completed',
3241
                    'passed' => 'scorm_completed',
3242
                    'succeeded' => 'scorm_completed',
3243
                    'browsed' => 'scorm_completed',
3244
                ];
3245
3246
                if (isset($classStatus[$subtree['status']])) {
3247
                    $cssStatus = $classStatus[$subtree['status']];
3248
                }
3249
3250
                $title = Security::remove_XSS($subtree['title']);
3251
                unset($subtree['title']);
3252
3253
                if (empty($title)) {
3254
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3255
                }
3256
                $classStyle = null;
3257
                if ($subtree['id'] == $this->current) {
3258
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3259
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3260
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3261
                }
3262
                $subtree['title'] = $title;
3263
                $subtree['class'] = $classStyle.' '.$cssStatus;
3264
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3265
                $subtree['current_id'] = $myCurrentId;
3266
                $listNotParent[] = $subtree;
3267
            }
3268
        }
3269
3270
        $list['are_parents'] = $listParent;
3271
        $list['not_parents'] = $listNotParent;
3272
3273
        return $list;
3274
    }
3275
3276
    /**
3277
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3278
     *
3279
     * @param array $tree
3280
     * @param int   $id
3281
     * @param bool  $parent
3282
     *
3283
     * @return array HTML TOC ready to display
3284
     */
3285
    public function getChildrenToc($tree, $id, $parent = true)
3286
    {
3287
        if (empty($tree)) {
3288
            $tree = $this->get_toc();
3289
        }
3290
3291
        $dirTypes = self::getChapterTypes();
3292
        $mycurrentitemid = $this->get_current_item_id();
3293
        $list = [];
3294
        $classStatus = [
3295
            'not attempted' => 'scorm_not_attempted',
3296
            'incomplete' => 'scorm_not_attempted',
3297
            'failed' => 'scorm_failed',
3298
            'completed' => 'scorm_completed',
3299
            'passed' => 'scorm_completed',
3300
            'succeeded' => 'scorm_completed',
3301
            'browsed' => 'scorm_completed',
3302
        ];
3303
3304
        foreach ($tree as $subtree) {
3305
            $subtree['tree'] = null;
3306
3307
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3308
                if ($subtree['id'] == $this->current) {
3309
                    $subtree['current'] = 'active';
3310
                } else {
3311
                    $subtree['current'] = null;
3312
                }
3313
                if (isset($classStatus[$subtree['status']])) {
3314
                    $cssStatus = $classStatus[$subtree['status']];
3315
                }
3316
3317
                $title = Security::remove_XSS($subtree['title']);
3318
                unset($subtree['title']);
3319
                if (empty($title)) {
3320
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3321
                }
3322
3323
                $classStyle = null;
3324
                if ($subtree['id'] == $this->current) {
3325
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3326
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3327
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3328
                }
3329
3330
                if (in_array($subtree['type'], $dirTypes)) {
3331
                    $subtree['title'] = stripslashes($title);
3332
                } else {
3333
                    $subtree['title'] = $title;
3334
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3335
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3336
                    $subtree['current_id'] = $mycurrentitemid;
3337
                }
3338
                $list[] = $subtree;
3339
            }
3340
        }
3341
3342
        return $list;
3343
    }
3344
3345
    /**
3346
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3347
     *
3348
     * @param array $toc_list
3349
     *
3350
     * @return array HTML TOC ready to display
3351
     */
3352
    public function getListArrayToc($toc_list = [])
3353
    {
3354
        if (empty($toc_list)) {
3355
            $toc_list = $this->get_toc();
3356
        }
3357
        // Temporary variables.
3358
        $mycurrentitemid = $this->get_current_item_id();
3359
        $list = [];
3360
        $arrayList = [];
3361
        $classStatus = [
3362
            'not attempted' => 'scorm_not_attempted',
3363
            'incomplete' => 'scorm_not_attempted',
3364
            'failed' => 'scorm_failed',
3365
            'completed' => 'scorm_completed',
3366
            'passed' => 'scorm_completed',
3367
            'succeeded' => 'scorm_completed',
3368
            'browsed' => 'scorm_completed',
3369
        ];
3370
3371
        foreach ($toc_list as $item) {
3372
            $list['id'] = $item['id'];
3373
            $list['status'] = $item['status'];
3374
            $cssStatus = null;
3375
3376
            if (isset($classStatus[$item['status']])) {
3377
                $cssStatus = $classStatus[$item['status']];
3378
            }
3379
3380
            $classStyle = ' ';
3381
            $dirTypes = self::getChapterTypes();
3382
3383
            if (in_array($item['type'], $dirTypes)) {
3384
                $classStyle = 'scorm_item_section ';
3385
            }
3386
            if ($item['id'] == $this->current) {
3387
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3388
            } elseif (!in_array($item['type'], $dirTypes)) {
3389
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3390
            }
3391
            $title = $item['title'];
3392
            if (empty($title)) {
3393
                $title = self::rl_get_resource_name(
3394
                    api_get_course_id(),
3395
                    $this->get_id(),
3396
                    $item['id']
3397
                );
3398
            }
3399
            $title = Security::remove_XSS($item['title']);
3400
3401
            if (empty($item['description'])) {
3402
                $list['description'] = $title;
3403
            } else {
3404
                $list['description'] = $item['description'];
3405
            }
3406
3407
            $list['class'] = $classStyle.' '.$cssStatus;
3408
            $list['level'] = $item['level'];
3409
            $list['type'] = $item['type'];
3410
3411
            if (in_array($item['type'], $dirTypes)) {
3412
                $list['css_level'] = 'level_'.$item['level'];
3413
            } else {
3414
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3415
            }
3416
3417
            if (in_array($item['type'], $dirTypes)) {
3418
                $list['title'] = stripslashes($title);
3419
            } else {
3420
                $list['title'] = stripslashes($title);
3421
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3422
                $list['current_id'] = $mycurrentitemid;
3423
            }
3424
            $arrayList[] = $list;
3425
        }
3426
3427
        return $arrayList;
3428
    }
3429
3430
    /**
3431
     * Returns an HTML-formatted string ready to display with teacher buttons
3432
     * in LP view menu.
3433
     *
3434
     * @return string HTML TOC ready to display
3435
     */
3436
    public function get_teacher_toc_buttons()
3437
    {
3438
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3439
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3440
        $html = '';
3441
        if ($isAllow && $hideIcons == false) {
3442
            if ($this->get_lp_session_id() == api_get_session_id()) {
3443
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3444
                $html .= '<div class="btn-group">';
3445
                $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'>".
3446
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3447
                $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'>".
3448
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3449
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3450
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3451
                $html .= '</div>';
3452
                $html .= '</div>';
3453
            }
3454
        }
3455
3456
        return $html;
3457
    }
3458
3459
    /**
3460
     * Gets the learnpath maker name - generally the editor's name.
3461
     *
3462
     * @return string Learnpath maker name
3463
     */
3464
    public function get_maker()
3465
    {
3466
        if (!empty($this->maker)) {
3467
            return $this->maker;
3468
        }
3469
3470
        return '';
3471
    }
3472
3473
    /**
3474
     * Gets the learnpath name/title.
3475
     *
3476
     * @return string Learnpath name/title
3477
     */
3478
    public function get_name()
3479
    {
3480
        if (!empty($this->name)) {
3481
            return $this->name;
3482
        }
3483
3484
        return 'N/A';
3485
    }
3486
3487
    /**
3488
     * Gets a link to the resource from the present location, depending on item ID.
3489
     *
3490
     * @param string $type         Type of link expected
3491
     * @param int    $item_id      Learnpath item ID
3492
     * @param bool   $provided_toc
3493
     *
3494
     * @return string $provided_toc Link to the lp_item resource
3495
     */
3496
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3497
    {
3498
        $course_id = $this->get_course_int_id();
3499
        if ($this->debug > 0) {
3500
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3501
        }
3502
        if (empty($item_id)) {
3503
            if ($this->debug > 2) {
3504
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3505
                error_log('using current: '.$this->get_current_item_id(), 0);
3506
            }
3507
            $item_id = $this->get_current_item_id();
3508
3509
            if (empty($item_id)) {
3510
                if ($this->debug > 2) {
3511
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3512
                }
3513
                //still empty, this means there was no item_id given and we are not in an object context or
3514
                //the object property is empty, return empty link
3515
                $this->first();
3516
3517
                return '';
3518
            }
3519
        }
3520
3521
        $file = '';
3522
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3523
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3524
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3525
        $item_id = (int) $item_id;
3526
3527
        $sql = "SELECT
3528
                    l.lp_type as ltype,
3529
                    l.path as lpath,
3530
                    li.item_type as litype,
3531
                    li.path as lipath,
3532
                    li.parameters as liparams
3533
        		FROM $lp_table l
3534
                INNER JOIN $lp_item_table li
3535
                ON (li.lp_id = l.iid)
3536
        		WHERE
3537
        		    li.iid = $item_id
3538
        		";
3539
        if ($this->debug > 2) {
3540
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3541
        }
3542
        $res = Database::query($sql);
3543
        if (Database::num_rows($res) > 0) {
3544
            $row = Database::fetch_array($res);
3545
            $lp_type = $row['ltype'];
3546
            $lp_path = $row['lpath'];
3547
            $lp_item_type = $row['litype'];
3548
            $lp_item_path = $row['lipath'];
3549
            $lp_item_params = $row['liparams'];
3550
3551
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3552
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3553
            }
3554
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3555
            if ($type === 'http') {
3556
                //web path
3557
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3558
            } else {
3559
                $course_path = $sys_course_path; //system path
3560
            }
3561
3562
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3563
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3564
            if (in_array(
3565
                $lp_item_type,
3566
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3567
            )
3568
            ) {
3569
                $lp_type = 1;
3570
            }
3571
3572
            if ($this->debug > 2) {
3573
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3574
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3575
            }
3576
3577
            // Now go through the specific cases to get the end of the path
3578
            // @todo Use constants instead of int values.
3579
            switch ($lp_type) {
3580
                case 1:
3581
                    $file = self::rl_get_resource_link_for_learnpath(
3582
                        $course_id,
3583
                        $this->get_id(),
3584
                        $item_id,
3585
                        $this->get_view_id()
3586
                    );
3587
                    if ($this->debug > 0) {
3588
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3589
                    }
3590
                    switch ($lp_item_type) {
3591
                        case 'document':
3592
                            // Shows a button to download the file instead of just downloading the file directly.
3593
                            $documentPathInfo = pathinfo($file);
3594
                            if (isset($documentPathInfo['extension'])) {
3595
                                $parsed = parse_url($documentPathInfo['extension']);
3596
                                if (isset($parsed['path'])) {
3597
                                    $extension = $parsed['path'];
3598
                                    $extensionsToDownload = [
3599
                                        'zip',
3600
                                        'ppt',
3601
                                        'pptx',
3602
                                        'ods',
3603
                                        'xlsx',
3604
                                        'xls',
3605
                                        'csv',
3606
                                        'doc',
3607
                                        'docx',
3608
                                        'dot',
3609
                                    ];
3610
3611
                                    if (in_array($extension, $extensionsToDownload)) {
3612
                                        $file = api_get_path(WEB_CODE_PATH).
3613
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3614
                                    }
3615
                                }
3616
                            }
3617
                            break;
3618
                        case 'dir':
3619
                            $file = 'lp_content.php?type=dir';
3620
                            break;
3621
                        case 'link':
3622
                            if (Link::is_youtube_link($file)) {
3623
                                $src = Link::get_youtube_video_id($file);
3624
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3625
                            } elseif (Link::isVimeoLink($file)) {
3626
                                $src = Link::getVimeoLinkId($file);
3627
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3628
                            } else {
3629
                                // If the current site is HTTPS and the link is
3630
                                // HTTP, browsers will refuse opening the link
3631
                                $urlId = api_get_current_access_url_id();
3632
                                $url = api_get_access_url($urlId, false);
3633
                                $protocol = substr($url['url'], 0, 5);
3634
                                if ($protocol === 'https') {
3635
                                    $linkProtocol = substr($file, 0, 5);
3636
                                    if ($linkProtocol === 'http:') {
3637
                                        //this is the special intervention case
3638
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3639
                                    }
3640
                                }
3641
                            }
3642
                            break;
3643
                        case 'quiz':
3644
                            // Check how much attempts of a exercise exits in lp
3645
                            $lp_item_id = $this->get_current_item_id();
3646
                            $lp_view_id = $this->get_view_id();
3647
3648
                            $prevent_reinit = null;
3649
                            if (isset($this->items[$this->current])) {
3650
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3651
                            }
3652
3653
                            if (empty($provided_toc)) {
3654
                                if ($this->debug > 0) {
3655
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3656
                                }
3657
                                $list = $this->get_toc();
3658
                            } else {
3659
                                if ($this->debug > 0) {
3660
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3661
                                }
3662
                                $list = $provided_toc;
3663
                            }
3664
3665
                            $type_quiz = false;
3666
                            foreach ($list as $toc) {
3667
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3668
                                    $type_quiz = true;
3669
                                }
3670
                            }
3671
3672
                            if ($type_quiz) {
3673
                                $lp_item_id = (int) $lp_item_id;
3674
                                $lp_view_id = (int) $lp_view_id;
3675
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3676
                                        WHERE
3677
                                            c_id = $course_id AND
3678
                                            lp_item_id='".$lp_item_id."' AND
3679
                                            lp_view_id ='".$lp_view_id."' AND
3680
                                            status='completed'";
3681
                                $result = Database::query($sql);
3682
                                $row_count = Database:: fetch_row($result);
3683
                                $count_item_view = (int) $row_count[0];
3684
                                $not_multiple_attempt = 0;
3685
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3686
                                    $not_multiple_attempt = 1;
3687
                                }
3688
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3689
                            }
3690
                            break;
3691
                    }
3692
3693
                    $tmp_array = explode('/', $file);
3694
                    $document_name = $tmp_array[count($tmp_array) - 1];
3695
                    if (strpos($document_name, '_DELETED_')) {
3696
                        $file = 'blank.php?error=document_deleted';
3697
                    }
3698
                    break;
3699
                case 2:
3700
                    if ($this->debug > 2) {
3701
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3702
                    }
3703
3704
                    if ($lp_item_type != 'dir') {
3705
                        // Quite complex here:
3706
                        // We want to make sure 'http://' (and similar) links can
3707
                        // be loaded as is (withouth the Chamilo path in front) but
3708
                        // some contents use this form: resource.htm?resource=http://blablabla
3709
                        // which means we have to find a protocol at the path's start, otherwise
3710
                        // it should not be considered as an external URL.
3711
                        // if ($this->prerequisites_match($item_id)) {
3712
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3713
                            if ($this->debug > 2) {
3714
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3715
                            }
3716
                            // Distant url, return as is.
3717
                            $file = $lp_item_path;
3718
                        } else {
3719
                            if ($this->debug > 2) {
3720
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3721
                            }
3722
                            // Prevent getting untranslatable urls.
3723
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3724
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3725
                            // Prepare the path.
3726
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3727
                            // TODO: Fix this for urls with protocol header.
3728
                            $file = str_replace('//', '/', $file);
3729
                            $file = str_replace(':/', '://', $file);
3730
                            if (substr($lp_path, -1) == '/') {
3731
                                $lp_path = substr($lp_path, 0, -1);
3732
                            }
3733
3734
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3735
                                // if file not found.
3736
                                $decoded = html_entity_decode($lp_item_path);
3737
                                list($decoded) = explode('?', $decoded);
3738
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3739
                                    $file = self::rl_get_resource_link_for_learnpath(
3740
                                        $course_id,
3741
                                        $this->get_id(),
3742
                                        $item_id,
3743
                                        $this->get_view_id()
3744
                                    );
3745
                                    if (empty($file)) {
3746
                                        $file = 'blank.php?error=document_not_found';
3747
                                    } else {
3748
                                        $tmp_array = explode('/', $file);
3749
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3750
                                        if (strpos($document_name, '_DELETED_')) {
3751
                                            $file = 'blank.php?error=document_deleted';
3752
                                        } else {
3753
                                            $file = 'blank.php?error=document_not_found';
3754
                                        }
3755
                                    }
3756
                                } else {
3757
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3758
                                }
3759
                            }
3760
                        }
3761
3762
                        // We want to use parameters if they were defined in the imsmanifest
3763
                        if (strpos($file, 'blank.php') === false) {
3764
                            $lp_item_params = ltrim($lp_item_params, '?');
3765
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3766
                        }
3767
                    } else {
3768
                        $file = 'lp_content.php?type=dir';
3769
                    }
3770
                    break;
3771
                case 3:
3772
                    if ($this->debug > 2) {
3773
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3774
                    }
3775
                    // Formatting AICC HACP append URL.
3776
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3777
                    if (!empty($lp_item_params)) {
3778
                        $aicc_append .= $lp_item_params.'&';
3779
                    }
3780
                    if ($lp_item_type != 'dir') {
3781
                        // Quite complex here:
3782
                        // We want to make sure 'http://' (and similar) links can
3783
                        // be loaded as is (withouth the Chamilo path in front) but
3784
                        // some contents use this form: resource.htm?resource=http://blablabla
3785
                        // which means we have to find a protocol at the path's start, otherwise
3786
                        // it should not be considered as an external URL.
3787
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3788
                            if ($this->debug > 2) {
3789
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3790
                            }
3791
                            // Distant url, return as is.
3792
                            $file = $lp_item_path;
3793
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3794
                            /*
3795
                            if (stristr($file,'<servername>') !== false) {
3796
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3797
                            }
3798
                            */
3799
                            if (stripos($file, '<servername>') !== false) {
3800
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3801
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3802
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3803
                            }
3804
3805
                            $file .= $aicc_append;
3806
                        } else {
3807
                            if ($this->debug > 2) {
3808
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3809
                            }
3810
                            // Prevent getting untranslatable urls.
3811
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3812
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3813
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3814
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3815
                            // TODO: Fix this for urls with protocol header.
3816
                            $file = str_replace('//', '/', $file);
3817
                            $file = str_replace(':/', '://', $file);
3818
                            $file .= $aicc_append;
3819
                        }
3820
                    } else {
3821
                        $file = 'lp_content.php?type=dir';
3822
                    }
3823
                    break;
3824
                case 4:
3825
                    break;
3826
                default:
3827
                    break;
3828
            }
3829
            // Replace &amp; by & because &amp; will break URL with params
3830
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3831
        }
3832
        if ($this->debug > 2) {
3833
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3834
        }
3835
3836
        return $file;
3837
    }
3838
3839
    /**
3840
     * Gets the latest usable view or generate a new one.
3841
     *
3842
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3843
     *
3844
     * @return int DB lp_view id
3845
     */
3846
    public function get_view($attempt_num = 0)
3847
    {
3848
        $search = '';
3849
        // Use $attempt_num to enable multi-views management (disabled so far).
3850
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3851
            $search = 'AND view_count = '.$attempt_num;
3852
        }
3853
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3854
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3855
3856
        $course_id = api_get_course_int_id();
3857
        $sessionId = api_get_session_id();
3858
3859
        $sql = "SELECT iid, view_count FROM $lp_view_table
3860
        		WHERE
3861
        		    c_id = $course_id AND
3862
        		    lp_id = ".$this->get_id()." AND
3863
        		    user_id = ".$this->get_user_id()." AND
3864
        		    session_id = $sessionId
3865
        		    $search
3866
                ORDER BY view_count DESC";
3867
        $res = Database::query($sql);
3868
        if (Database::num_rows($res) > 0) {
3869
            $row = Database::fetch_array($res);
3870
            $this->lp_view_id = $row['iid'];
3871
        } elseif (!api_is_invitee()) {
3872
            // There is no database record, create one.
3873
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3874
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3875
            Database::query($sql);
3876
            $id = Database::insert_id();
3877
            $this->lp_view_id = $id;
3878
3879
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3880
            Database::query($sql);
3881
        }
3882
3883
        return $this->lp_view_id;
3884
    }
3885
3886
    /**
3887
     * Gets the current view id.
3888
     *
3889
     * @return int View ID (from lp_view)
3890
     */
3891
    public function get_view_id()
3892
    {
3893
        if (!empty($this->lp_view_id)) {
3894
            return (int) $this->lp_view_id;
3895
        }
3896
3897
        return 0;
3898
    }
3899
3900
    /**
3901
     * Gets the update queue.
3902
     *
3903
     * @return array Array containing IDs of items to be updated by JavaScript
3904
     */
3905
    public function get_update_queue()
3906
    {
3907
        return $this->update_queue;
3908
    }
3909
3910
    /**
3911
     * Gets the user ID.
3912
     *
3913
     * @return int User ID
3914
     */
3915
    public function get_user_id()
3916
    {
3917
        if (!empty($this->user_id)) {
3918
            return (int) $this->user_id;
3919
        }
3920
3921
        return false;
3922
    }
3923
3924
    /**
3925
     * Checks if any of the items has an audio element attached.
3926
     *
3927
     * @return bool True or false
3928
     */
3929
    public function has_audio()
3930
    {
3931
        $has = false;
3932
        foreach ($this->items as $i => $item) {
3933
            if (!empty($this->items[$i]->audio)) {
3934
                $has = true;
3935
                break;
3936
            }
3937
        }
3938
3939
        return $has;
3940
    }
3941
3942
    /**
3943
     * Moves an item up and down at its level.
3944
     *
3945
     * @param int    $id        Item to move up and down
3946
     * @param string $direction Direction 'up' or 'down'
3947
     *
3948
     * @return bool|int
3949
     */
3950
    public function move_item($id, $direction)
3951
    {
3952
        $course_id = api_get_course_int_id();
3953
        if ($this->debug > 0) {
3954
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
3955
        }
3956
        if (empty($id) || empty($direction)) {
3957
            return false;
3958
        }
3959
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3960
        $sql_sel = "SELECT *
3961
                    FROM $tbl_lp_item
3962
                    WHERE
3963
                        iid = $id
3964
                    ";
3965
        $res_sel = Database::query($sql_sel);
3966
        // Check if elem exists.
3967
        if (Database::num_rows($res_sel) < 1) {
3968
            return false;
3969
        }
3970
        // Gather data.
3971
        $row = Database::fetch_array($res_sel);
3972
        $previous = $row['previous_item_id'];
3973
        $next = $row['next_item_id'];
3974
        $display = $row['display_order'];
3975
        $parent = $row['parent_item_id'];
3976
        $lp = $row['lp_id'];
3977
        // Update the item (switch with previous/next one).
3978
        switch ($direction) {
3979
            case 'up':
3980
                if ($this->debug > 2) {
3981
                    error_log('Movement up detected', 0);
3982
                }
3983
                if ($display > 1) {
3984
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3985
                                 WHERE iid = $previous";
3986
3987
                    if ($this->debug > 2) {
3988
                        error_log('Selecting previous: '.$sql_sel2, 0);
3989
                    }
3990
                    $res_sel2 = Database::query($sql_sel2);
3991
                    if (Database::num_rows($res_sel2) < 1) {
3992
                        $previous_previous = 0;
3993
                    }
3994
                    // Gather data.
3995
                    $row2 = Database::fetch_array($res_sel2);
3996
                    $previous_previous = $row2['previous_item_id'];
3997
                    // Update previous_previous item (switch "next" with current).
3998
                    if ($previous_previous != 0) {
3999
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4000
                                        next_item_id = $id
4001
                                    WHERE iid = $previous_previous";
4002
                        if ($this->debug > 2) {
4003
                            error_log($sql_upd2, 0);
4004
                        }
4005
                        Database::query($sql_upd2);
4006
                    }
4007
                    // Update previous item (switch with current).
4008
                    if ($previous != 0) {
4009
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4010
                                    next_item_id = $next,
4011
                                    previous_item_id = $id,
4012
                                    display_order = display_order +1
4013
                                    WHERE iid = $previous";
4014
                        if ($this->debug > 2) {
4015
                            error_log($sql_upd2, 0);
4016
                        }
4017
                        Database::query($sql_upd2);
4018
                    }
4019
4020
                    // Update current item (switch with previous).
4021
                    if ($id != 0) {
4022
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4023
                                        next_item_id = $previous,
4024
                                        previous_item_id = $previous_previous,
4025
                                        display_order = display_order-1
4026
                                    WHERE c_id = ".$course_id." AND id = $id";
4027
                        if ($this->debug > 2) {
4028
                            error_log($sql_upd2, 0);
4029
                        }
4030
                        Database::query($sql_upd2);
4031
                    }
4032
                    // Update next item (new previous item).
4033
                    if (!empty($next)) {
4034
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4035
                                     WHERE iid = $next";
4036
                        if ($this->debug > 2) {
4037
                            error_log($sql_upd2, 0);
4038
                        }
4039
                        Database::query($sql_upd2);
4040
                    }
4041
                    $display = $display - 1;
4042
                }
4043
                break;
4044
            case 'down':
4045
                if ($this->debug > 2) {
4046
                    error_log('Movement down detected', 0);
4047
                }
4048
                if ($next != 0) {
4049
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4050
                                 WHERE iid = $next";
4051
                    if ($this->debug > 2) {
4052
                        error_log('Selecting next: '.$sql_sel2, 0);
4053
                    }
4054
                    $res_sel2 = Database::query($sql_sel2);
4055
                    if (Database::num_rows($res_sel2) < 1) {
4056
                        $next_next = 0;
4057
                    }
4058
                    // Gather data.
4059
                    $row2 = Database::fetch_array($res_sel2);
4060
                    $next_next = $row2['next_item_id'];
4061
                    // Update previous item (switch with current).
4062
                    if ($previous != 0) {
4063
                        $sql_upd2 = "UPDATE $tbl_lp_item
4064
                                     SET next_item_id = $next
4065
                                     WHERE iid = $previous";
4066
                        Database::query($sql_upd2);
4067
                    }
4068
                    // Update current item (switch with previous).
4069
                    if ($id != 0) {
4070
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4071
                                     previous_item_id = $next,
4072
                                     next_item_id = $next_next,
4073
                                     display_order = display_order + 1
4074
                                     WHERE iid = $id";
4075
                        Database::query($sql_upd2);
4076
                    }
4077
4078
                    // Update next item (new previous item).
4079
                    if ($next != 0) {
4080
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4081
                                     previous_item_id = $previous,
4082
                                     next_item_id = $id,
4083
                                     display_order = display_order-1
4084
                                     WHERE iid = $next";
4085
                        Database::query($sql_upd2);
4086
                    }
4087
4088
                    // Update next_next item (switch "previous" with current).
4089
                    if ($next_next != 0) {
4090
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4091
                                     previous_item_id = $id
4092
                                     WHERE iid = $next_next";
4093
                        Database::query($sql_upd2);
4094
                    }
4095
                    $display = $display + 1;
4096
                }
4097
                break;
4098
            default:
4099
                return false;
4100
        }
4101
4102
        return $display;
4103
    }
4104
4105
    /**
4106
     * Move a LP up (display_order).
4107
     *
4108
     * @param int $lp_id      Learnpath ID
4109
     * @param int $categoryId Category ID
4110
     *
4111
     * @return bool
4112
     */
4113
    public static function move_up($lp_id, $categoryId = 0)
4114
    {
4115
        $courseId = api_get_course_int_id();
4116
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4117
4118
        $categoryCondition = '';
4119
        if (!empty($categoryId)) {
4120
            $categoryId = (int) $categoryId;
4121
            $categoryCondition = " AND category_id = $categoryId";
4122
        }
4123
        $sql = "SELECT * FROM $lp_table
4124
                WHERE c_id = $courseId
4125
                $categoryCondition
4126
                ORDER BY display_order";
4127
        $res = Database::query($sql);
4128
        if ($res === false) {
4129
            return false;
4130
        }
4131
4132
        $lps = [];
4133
        $lp_order = [];
4134
        $num = Database::num_rows($res);
4135
        // First check the order is correct, globally (might be wrong because
4136
        // of versions < 1.8.4)
4137
        if ($num > 0) {
4138
            $i = 1;
4139
            while ($row = Database::fetch_array($res)) {
4140
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4141
                    $sql = "UPDATE $lp_table SET display_order = $i
4142
                            WHERE iid = ".$row['iid'];
4143
                    Database::query($sql);
4144
                }
4145
                $row['display_order'] = $i;
4146
                $lps[$row['iid']] = $row;
4147
                $lp_order[$i] = $row['iid'];
4148
                $i++;
4149
            }
4150
        }
4151
        if ($num > 1) { // If there's only one element, no need to sort.
4152
            $order = $lps[$lp_id]['display_order'];
4153
            if ($order > 1) { // If it's the first element, no need to move up.
4154
                $sql = "UPDATE $lp_table SET display_order = $order
4155
                        WHERE iid = ".$lp_order[$order - 1];
4156
                Database::query($sql);
4157
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4158
                        WHERE iid = $lp_id";
4159
                Database::query($sql);
4160
            }
4161
        }
4162
4163
        return true;
4164
    }
4165
4166
    /**
4167
     * Move a learnpath down (display_order).
4168
     *
4169
     * @param int $lp_id      Learnpath ID
4170
     * @param int $categoryId Category ID
4171
     *
4172
     * @return bool
4173
     */
4174
    public static function move_down($lp_id, $categoryId = 0)
4175
    {
4176
        $courseId = api_get_course_int_id();
4177
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4178
4179
        $categoryCondition = '';
4180
        if (!empty($categoryId)) {
4181
            $categoryId = (int) $categoryId;
4182
            $categoryCondition = " AND category_id = $categoryId";
4183
        }
4184
4185
        $sql = "SELECT * FROM $lp_table
4186
                WHERE c_id = $courseId
4187
                $categoryCondition
4188
                ORDER BY display_order";
4189
        $res = Database::query($sql);
4190
        if ($res === false) {
4191
            return false;
4192
        }
4193
        $lps = [];
4194
        $lp_order = [];
4195
        $num = Database::num_rows($res);
4196
        $max = 0;
4197
        // First check the order is correct, globally (might be wrong because
4198
        // of versions < 1.8.4).
4199
        if ($num > 0) {
4200
            $i = 1;
4201
            while ($row = Database::fetch_array($res)) {
4202
                $max = $i;
4203
                if ($row['display_order'] != $i) {
4204
                    // If we find a gap in the order, we need to fix it.
4205
                    $sql = "UPDATE $lp_table SET display_order = $i
4206
                              WHERE iid = ".$row['iid'];
4207
                    Database::query($sql);
4208
                }
4209
                $row['display_order'] = $i;
4210
                $lps[$row['iid']] = $row;
4211
                $lp_order[$i] = $row['iid'];
4212
                $i++;
4213
            }
4214
        }
4215
        if ($num > 1) { // If there's only one element, no need to sort.
4216
            $order = $lps[$lp_id]['display_order'];
4217
            if ($order < $max) { // If it's the first element, no need to move up.
4218
                $sql = "UPDATE $lp_table SET display_order = $order
4219
                        WHERE iid = ".$lp_order[$order + 1];
4220
                Database::query($sql);
4221
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4222
                        WHERE iid = $lp_id";
4223
                Database::query($sql);
4224
            }
4225
        }
4226
4227
        return true;
4228
    }
4229
4230
    /**
4231
     * Updates learnpath attributes to point to the next element
4232
     * The last part is similar to set_current_item but processing the other way around.
4233
     */
4234
    public function next()
4235
    {
4236
        if ($this->debug > 0) {
4237
            error_log('In learnpath::next()', 0);
4238
        }
4239
        $this->last = $this->get_current_item_id();
4240
        $this->items[$this->last]->save(
4241
            false,
4242
            $this->prerequisites_match($this->last)
4243
        );
4244
        $this->autocomplete_parents($this->last);
4245
        $new_index = $this->get_next_index();
4246
        if ($this->debug > 2) {
4247
            error_log('New index: '.$new_index, 0);
4248
        }
4249
        $this->index = $new_index;
4250
        if ($this->debug > 2) {
4251
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4252
        }
4253
        $this->current = $this->ordered_items[$new_index];
4254
        if ($this->debug > 2) {
4255
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4256
        }
4257
    }
4258
4259
    /**
4260
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4261
     * class, this might be redefined to allow several behaviours depending on the document type.
4262
     *
4263
     * @param int $id Resource ID
4264
     */
4265
    public function open($id)
4266
    {
4267
        if ($this->debug > 0) {
4268
            error_log('In learnpath::open()', 0);
4269
        }
4270
        // TODO:
4271
        // set the current resource attribute to this resource
4272
        // switch on element type (redefine in child class?)
4273
        // set status for this item to "opened"
4274
        // start timer
4275
        // initialise score
4276
        $this->index = 0; //or = the last item seen (see $this->last)
4277
    }
4278
4279
    /**
4280
     * Check that all prerequisites are fulfilled. Returns true and an
4281
     * empty string on success, returns false
4282
     * and the prerequisite string on error.
4283
     * This function is based on the rules for aicc_script language as
4284
     * described in the SCORM 1.2 CAM documentation page 108.
4285
     *
4286
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4287
     *
4288
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4289
     *              string otherwise
4290
     */
4291
    public function prerequisites_match($itemId = null)
4292
    {
4293
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4294
        if ($allow) {
4295
            if (api_is_allowed_to_edit() ||
4296
                api_is_platform_admin(true) ||
4297
                api_is_drh() ||
4298
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4299
            ) {
4300
                return true;
4301
            }
4302
        }
4303
4304
        $debug = $this->debug;
4305
        if ($debug > 0) {
4306
            error_log('In learnpath::prerequisites_match()');
4307
        }
4308
4309
        if (empty($itemId)) {
4310
            $itemId = $this->current;
4311
        }
4312
4313
        $currentItem = $this->getItem($itemId);
4314
4315
        if ($currentItem) {
4316
            if ($this->type == 2) {
4317
                // Getting prereq from scorm
4318
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4319
            } else {
4320
                $prereq_string = $currentItem->get_prereq_string();
4321
            }
4322
4323
            if (empty($prereq_string)) {
4324
                if ($debug > 0) {
4325
                    error_log('Found prereq_string is empty return true');
4326
                }
4327
4328
                return true;
4329
            }
4330
4331
            // Clean spaces.
4332
            $prereq_string = str_replace(' ', '', $prereq_string);
4333
            if ($debug > 0) {
4334
                error_log('Found prereq_string: '.$prereq_string, 0);
4335
            }
4336
4337
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4338
            $result = $currentItem->parse_prereq(
4339
                $prereq_string,
4340
                $this->items,
4341
                $this->refs_list,
4342
                $this->get_user_id()
4343
            );
4344
4345
            if ($result === false) {
4346
                $this->set_error_msg($currentItem->prereq_alert);
4347
            }
4348
        } else {
4349
            $result = true;
4350
            if ($debug > 1) {
4351
                error_log('$this->items['.$itemId.'] was not an object', 0);
4352
            }
4353
        }
4354
4355
        if ($debug > 1) {
4356
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4357
        }
4358
4359
        return $result;
4360
    }
4361
4362
    /**
4363
     * Updates learnpath attributes to point to the previous element
4364
     * The last part is similar to set_current_item but processing the other way around.
4365
     */
4366
    public function previous()
4367
    {
4368
        $this->last = $this->get_current_item_id();
4369
        $this->items[$this->last]->save(
4370
            false,
4371
            $this->prerequisites_match($this->last)
4372
        );
4373
        $this->autocomplete_parents($this->last);
4374
        $new_index = $this->get_previous_index();
4375
        $this->index = $new_index;
4376
        $this->current = $this->ordered_items[$new_index];
4377
    }
4378
4379
    /**
4380
     * Publishes a learnpath. This basically means show or hide the learnpath
4381
     * to normal users.
4382
     * Can be used as abstract.
4383
     *
4384
     * @param int $lp_id          Learnpath ID
4385
     * @param int $set_visibility New visibility
4386
     *
4387
     * @return bool
4388
     */
4389
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4390
    {
4391
        $action = 'visible';
4392
        if ($set_visibility != 1) {
4393
            $action = 'invisible';
4394
            self::toggle_publish($lp_id, 'i');
4395
        }
4396
4397
        return api_item_property_update(
4398
            api_get_course_info(),
4399
            TOOL_LEARNPATH,
4400
            $lp_id,
4401
            $action,
4402
            api_get_user_id()
4403
        );
4404
    }
4405
4406
    /**
4407
     * Publishes a learnpath category.
4408
     * This basically means show or hide the learnpath category to normal users.
4409
     *
4410
     * @param int $id
4411
     * @param int $visibility
4412
     *
4413
     * @throws \Doctrine\ORM\NonUniqueResultException
4414
     * @throws \Doctrine\ORM\ORMException
4415
     * @throws \Doctrine\ORM\OptimisticLockException
4416
     * @throws \Doctrine\ORM\TransactionRequiredException
4417
     *
4418
     * @return bool
4419
     */
4420
    public static function toggleCategoryVisibility($id, $visibility = 1)
4421
    {
4422
        $action = 'visible';
4423
4424
        if ($visibility != 1) {
4425
            $action = 'invisible';
4426
            $list = new LearnpathList(
4427
                api_get_user_id(),
4428
                null,
4429
                null,
4430
                null,
4431
                false,
4432
                $id
4433
            );
4434
4435
            $lpList = $list->get_flat_list();
4436
            foreach ($lpList as $lp) {
4437
                self::toggle_visibility($lp['iid'], 0);
4438
            }
4439
4440
            self::toggleCategoryPublish($id, 0);
4441
        }
4442
4443
        return api_item_property_update(
4444
            api_get_course_info(),
4445
            TOOL_LEARNPATH_CATEGORY,
4446
            $id,
4447
            $action,
4448
            api_get_user_id()
4449
        );
4450
    }
4451
4452
    /**
4453
     * Publishes a learnpath. This basically means show or hide the learnpath
4454
     * on the course homepage
4455
     * Can be used as abstract.
4456
     *
4457
     * @param int    $lp_id          Learnpath id
4458
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4459
     *
4460
     * @return bool
4461
     */
4462
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4463
    {
4464
        $course_id = api_get_course_int_id();
4465
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4466
        $lp_id = (int) $lp_id;
4467
        $sql = "SELECT * FROM $tbl_lp
4468
                WHERE iid = $lp_id";
4469
        $result = Database::query($sql);
4470
        if (Database::num_rows($result)) {
4471
            $row = Database::fetch_array($result);
4472
            $name = Database::escape_string($row['name']);
4473
            if ($set_visibility == 'i') {
4474
                $v = 0;
4475
            }
4476
            if ($set_visibility == 'v') {
4477
                $v = 1;
4478
            }
4479
4480
            $session_id = api_get_session_id();
4481
            $session_condition = api_get_session_condition($session_id);
4482
4483
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4484
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4485
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4486
4487
            $sql = "SELECT * FROM $tbl_tool
4488
                    WHERE
4489
                        c_id = $course_id AND
4490
                        (link = '$link' OR link = '$oldLink') AND
4491
                        image = 'scormbuilder.gif' AND
4492
                        (
4493
                            link LIKE '$link%' OR
4494
                            link LIKE '$oldLink%'
4495
                        )
4496
                        $session_condition
4497
                    ";
4498
4499
            $result = Database::query($sql);
4500
            $num = Database::num_rows($result);
4501
            if ($set_visibility == 'i' && $num > 0) {
4502
                $sql = "DELETE FROM $tbl_tool
4503
                        WHERE
4504
                            c_id = $course_id AND
4505
                            (link = '$link' OR link = '$oldLink') AND
4506
                            image='scormbuilder.gif'
4507
                            $session_condition";
4508
                Database::query($sql);
4509
            } elseif ($set_visibility == 'v' && $num == 0) {
4510
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4511
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4512
                Database::query($sql);
4513
                $insertId = Database::insert_id();
4514
                if ($insertId) {
4515
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4516
                    Database::query($sql);
4517
                }
4518
            } elseif ($set_visibility == 'v' && $num > 0) {
4519
                $sql = "UPDATE $tbl_tool SET
4520
                            c_id = $course_id,
4521
                            name = '$name',
4522
                            link = '$link',
4523
                            image = 'scormbuilder.gif',
4524
                            visibility = '$v',
4525
                            admin = '0',
4526
                            address = 'pastillegris.gif',
4527
                            added_tool = 0,
4528
                            session_id = $session_id
4529
                        WHERE
4530
                            c_id = ".$course_id." AND
4531
                            (link = '$link' OR link = '$oldLink') AND
4532
                            image='scormbuilder.gif'
4533
                            $session_condition
4534
                        ";
4535
                Database::query($sql);
4536
            } else {
4537
                // Parameter and database incompatible, do nothing, exit.
4538
                return false;
4539
            }
4540
        } else {
4541
            return false;
4542
        }
4543
    }
4544
4545
    /**
4546
     * Publishes a learnpath.
4547
     * Show or hide the learnpath category on the course homepage.
4548
     *
4549
     * @param int $id
4550
     * @param int $setVisibility
4551
     *
4552
     * @throws \Doctrine\ORM\NonUniqueResultException
4553
     * @throws \Doctrine\ORM\ORMException
4554
     * @throws \Doctrine\ORM\OptimisticLockException
4555
     * @throws \Doctrine\ORM\TransactionRequiredException
4556
     *
4557
     * @return bool
4558
     */
4559
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4560
    {
4561
        $courseId = api_get_course_int_id();
4562
        $sessionId = api_get_session_id();
4563
        $sessionCondition = api_get_session_condition(
4564
            $sessionId,
4565
            true,
4566
            false,
4567
            't.sessionId'
4568
        );
4569
4570
        $em = Database::getManager();
4571
4572
        /** @var CLpCategory $category */
4573
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4574
4575
        if (!$category) {
4576
            return false;
4577
        }
4578
4579
        if (empty($courseId)) {
4580
            return false;
4581
        }
4582
4583
        $link = self::getCategoryLinkForTool($id);
4584
4585
        /** @var CTool $tool */
4586
        $tool = $em->createQuery("
4587
                SELECT t FROM ChamiloCourseBundle:CTool t
4588
                WHERE
4589
                    t.course = :course AND
4590
                    t.link = :link1 AND
4591
                    t.image LIKE 'lp_category.%' AND
4592
                    t.link LIKE :link2
4593
                    $sessionCondition
4594
            ")
4595
            ->setParameters([
4596
                'course' => $courseId,
4597
                'link1' => $link,
4598
                'link2' => "$link%",
4599
            ])
4600
            ->getOneOrNullResult();
4601
4602
        if ($setVisibility == 0 && $tool) {
4603
            $em->remove($tool);
4604
            $em->flush();
4605
4606
            return true;
4607
        }
4608
4609
        if ($setVisibility == 1 && !$tool) {
4610
            $tool = new CTool();
4611
            $tool
4612
                ->setCategory('authoring')
4613
                ->setCourse(api_get_course_entity($courseId))
4614
                ->setName(strip_tags($category->getName()))
4615
                ->setLink($link)
4616
                ->setImage('lp_category.png')
4617
                ->setVisibility(1)
4618
                ->setAdmin(0)
4619
                ->setAddress('pastillegris.gif')
4620
                ->setAddedTool(0)
4621
                ->setSessionId($sessionId)
4622
                ->setTarget('_self');
4623
4624
            $em->persist($tool);
4625
            $em->flush();
4626
4627
            $tool->setId($tool->getIid());
4628
4629
            $em->persist($tool);
4630
            $em->flush();
4631
4632
            return true;
4633
        }
4634
4635
        if ($setVisibility == 1 && $tool) {
4636
            $tool
4637
                ->setName(strip_tags($category->getName()))
4638
                ->setVisibility(1);
4639
4640
            $em->persist($tool);
4641
            $em->flush();
4642
4643
            return true;
4644
        }
4645
4646
        return false;
4647
    }
4648
4649
    /**
4650
     * Check if the learnpath category is visible for a user.
4651
     *
4652
     * @param CLpCategory $category
4653
     * @param User        $user
4654
     * @param int
4655
     * @param int
4656
     *
4657
     * @return bool
4658
     */
4659
    public static function categoryIsVisibleForStudent(
4660
        CLpCategory $category,
4661
        User $user,
4662
        $courseId = 0,
4663
        $sessionId = 0
4664
    ) {
4665
        $subscriptionSettings = self::getSubscriptionSettings();
4666
4667
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4668
            return true;
4669
        }
4670
4671
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4672
4673
        if ($isAllowedToEdit) {
4674
            return true;
4675
        }
4676
4677
        if (empty($category)) {
4678
            return false;
4679
        }
4680
4681
        $users = $category->getUsers();
4682
4683
        if (empty($users) || !$users->count()) {
4684
            return true;
4685
        }
4686
4687
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4688
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4689
4690
        if ($category->hasUserAdded($user)) {
4691
            return true;
4692
        }
4693
4694
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4695
        if (!empty($groups)) {
4696
            $em = Database::getManager();
4697
4698
            /** @var ItemPropertyRepository $itemRepo */
4699
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4700
4701
            /** @var CourseRepository $courseRepo */
4702
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4703
            $session = null;
4704
            if (!empty($sessionId)) {
4705
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4706
            }
4707
4708
            $course = $courseRepo->find($courseId);
4709
4710
            // Subscribed groups to a LP
4711
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4712
                TOOL_LEARNPATH_CATEGORY,
4713
                $category->getId(),
4714
                $course,
4715
                $session
4716
            );
4717
4718
            if (!empty($subscribedGroupsInLp)) {
4719
                $groups = array_column($groups, 'iid');
4720
                /** @var CItemProperty $item */
4721
                foreach ($subscribedGroupsInLp as $item) {
4722
                    if ($item->getGroup() &&
4723
                        in_array($item->getGroup()->getId(), $groups)
4724
                    ) {
4725
                        return true;
4726
                    }
4727
                }
4728
            }
4729
        }
4730
4731
        return false;
4732
    }
4733
4734
    /**
4735
     * Check if a learnpath category is published as course tool.
4736
     *
4737
     * @param CLpCategory $category
4738
     * @param int         $courseId
4739
     *
4740
     * @return bool
4741
     */
4742
    public static function categoryIsPublished(
4743
        CLpCategory $category,
4744
        $courseId
4745
    ) {
4746
        $link = self::getCategoryLinkForTool($category->getId());
4747
        $em = Database::getManager();
4748
4749
        $tools = $em
4750
            ->createQuery("
4751
                SELECT t FROM ChamiloCourseBundle:CTool t
4752
                WHERE t.course = :course AND 
4753
                    t.name = :name AND
4754
                    t.image LIKE 'lp_category.%' AND
4755
                    t.link LIKE :link
4756
            ")
4757
            ->setParameters([
4758
                'course' => $courseId,
4759
                'name' => strip_tags($category->getName()),
4760
                'link' => "$link%",
4761
            ])
4762
            ->getResult();
4763
4764
        /** @var CTool $tool */
4765
        $tool = current($tools);
4766
4767
        return $tool ? $tool->getVisibility() : false;
4768
    }
4769
4770
    /**
4771
     * Restart the whole learnpath. Return the URL of the first element.
4772
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4773
     * To use a similar method  statically, use the create_new_attempt() method.
4774
     *
4775
     * @return bool
4776
     */
4777
    public function restart()
4778
    {
4779
        if ($this->debug > 0) {
4780
            error_log('In learnpath::restart()', 0);
4781
        }
4782
        // TODO
4783
        // Call autosave method to save the current progress.
4784
        //$this->index = 0;
4785
        if (api_is_invitee()) {
4786
            return false;
4787
        }
4788
        $session_id = api_get_session_id();
4789
        $course_id = api_get_course_int_id();
4790
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4791
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4792
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4793
        if ($this->debug > 2) {
4794
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4795
        }
4796
        Database::query($sql);
4797
        $view_id = Database::insert_id();
4798
4799
        if ($view_id) {
4800
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4801
            Database::query($sql);
4802
            $this->lp_view_id = $view_id;
4803
            $this->attempt = $this->attempt + 1;
4804
        } else {
4805
            $this->error = 'Could not insert into item_view table...';
4806
4807
            return false;
4808
        }
4809
        $this->autocomplete_parents($this->current);
4810
        foreach ($this->items as $index => $dummy) {
4811
            $this->items[$index]->restart();
4812
            $this->items[$index]->set_lp_view($this->lp_view_id);
4813
        }
4814
        $this->first();
4815
4816
        return true;
4817
    }
4818
4819
    /**
4820
     * Saves the current item.
4821
     *
4822
     * @return bool
4823
     */
4824
    public function save_current()
4825
    {
4826
        $debug = $this->debug;
4827
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4828
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4829
        if ($debug) {
4830
            error_log('save_current() saving item '.$this->current, 0);
4831
            error_log(''.print_r($this->items, true), 0);
4832
        }
4833
        if (isset($this->items[$this->current]) &&
4834
            is_object($this->items[$this->current])
4835
        ) {
4836
            if ($debug) {
4837
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4838
            }
4839
4840
            $res = $this->items[$this->current]->save(
4841
                false,
4842
                $this->prerequisites_match($this->current)
4843
            );
4844
            $this->autocomplete_parents($this->current);
4845
            $status = $this->items[$this->current]->get_status();
4846
            $this->update_queue[$this->current] = $status;
4847
4848
            if ($debug) {
4849
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4850
            }
4851
4852
            return $res;
4853
        }
4854
4855
        return false;
4856
    }
4857
4858
    /**
4859
     * Saves the given item.
4860
     *
4861
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4862
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4863
     *
4864
     * @return bool
4865
     */
4866
    public function save_item($item_id = null, $from_outside = true)
4867
    {
4868
        $debug = $this->debug;
4869
        if ($debug) {
4870
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4871
        }
4872
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4873
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4874
        if (empty($item_id)) {
4875
            $item_id = (int) $_REQUEST['id'];
4876
        }
4877
4878
        if (empty($item_id)) {
4879
            $item_id = $this->get_current_item_id();
4880
        }
4881
        if (isset($this->items[$item_id]) &&
4882
            is_object($this->items[$item_id])
4883
        ) {
4884
            if ($debug) {
4885
                error_log('Object exists');
4886
            }
4887
4888
            // Saving the item.
4889
            $res = $this->items[$item_id]->save(
4890
                $from_outside,
4891
                $this->prerequisites_match($item_id)
4892
            );
4893
4894
            if ($debug) {
4895
                error_log('update_queue before:');
4896
                error_log(print_r($this->update_queue, 1));
4897
            }
4898
            $this->autocomplete_parents($item_id);
4899
4900
            $status = $this->items[$item_id]->get_status();
4901
            $this->update_queue[$item_id] = $status;
4902
4903
            if ($debug) {
4904
                error_log('get_status(): '.$status);
4905
                error_log('update_queue after:');
4906
                error_log(print_r($this->update_queue, 1));
4907
            }
4908
4909
            return $res;
4910
        }
4911
4912
        return false;
4913
    }
4914
4915
    /**
4916
     * Saves the last item seen's ID only in case.
4917
     */
4918
    public function save_last()
4919
    {
4920
        $course_id = api_get_course_int_id();
4921
        $debug = $this->debug;
4922
        if ($debug) {
4923
            error_log('In learnpath::save_last()', 0);
4924
        }
4925
        $session_condition = api_get_session_condition(
4926
            api_get_session_id(),
4927
            true,
4928
            false
4929
        );
4930
        $table = Database::get_course_table(TABLE_LP_VIEW);
4931
4932
        if (isset($this->current) && !api_is_invitee()) {
4933
            if ($debug) {
4934
                error_log('Saving current item ('.$this->current.') for later review', 0);
4935
            }
4936
            $sql = "UPDATE $table SET
4937
                        last_item = ".$this->get_current_item_id()."
4938
                    WHERE
4939
                        c_id = $course_id AND
4940
                        lp_id = ".$this->get_id()." AND
4941
                        user_id = ".$this->get_user_id()." ".$session_condition;
4942
4943
            if ($debug) {
4944
                error_log('Saving last item seen : '.$sql, 0);
4945
            }
4946
            Database::query($sql);
4947
        }
4948
4949
        if (!api_is_invitee()) {
4950
            // Save progress.
4951
            list($progress) = $this->get_progress_bar_text('%');
4952
            if ($progress >= 0 && $progress <= 100) {
4953
                $progress = (int) $progress;
4954
                $sql = "UPDATE $table SET
4955
                            progress = $progress
4956
                        WHERE
4957
                            c_id = $course_id AND
4958
                            lp_id = ".$this->get_id()." AND
4959
                            user_id = ".$this->get_user_id()." ".$session_condition;
4960
                // Ignore errors as some tables might not have the progress field just yet.
4961
                Database::query($sql);
4962
                $this->progress_db = $progress;
4963
            }
4964
        }
4965
    }
4966
4967
    /**
4968
     * Sets the current item ID (checks if valid and authorized first).
4969
     *
4970
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4971
     */
4972
    public function set_current_item($item_id = null)
4973
    {
4974
        $debug = $this->debug;
4975
        if ($debug) {
4976
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4977
        }
4978
        if (empty($item_id)) {
4979
            if ($debug) {
4980
                error_log('No new current item given, ignore...', 0);
4981
            }
4982
            // Do nothing.
4983
        } else {
4984
            if ($debug) {
4985
                error_log('New current item given is '.$item_id.'...', 0);
4986
            }
4987
            if (is_numeric($item_id)) {
4988
                $item_id = (int) $item_id;
4989
                // TODO: Check in database here.
4990
                $this->last = $this->current;
4991
                $this->current = $item_id;
4992
                // TODO: Update $this->index as well.
4993
                foreach ($this->ordered_items as $index => $item) {
4994
                    if ($item == $this->current) {
4995
                        $this->index = $index;
4996
                        break;
4997
                    }
4998
                }
4999
                if ($debug) {
5000
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5001
                }
5002
            } else {
5003
                if ($debug) {
5004
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5005
                }
5006
            }
5007
        }
5008
    }
5009
5010
    /**
5011
     * Sets the encoding.
5012
     *
5013
     * @param string $enc New encoding
5014
     *
5015
     * @return bool
5016
     *
5017
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5018
     */
5019
    public function set_encoding($enc = 'UTF-8')
5020
    {
5021
        $enc = api_refine_encoding_id($enc);
5022
        if (empty($enc)) {
5023
            $enc = api_get_system_encoding();
5024
        }
5025
        if (api_is_encoding_supported($enc)) {
5026
            $lp = $this->get_id();
5027
            if ($lp != 0) {
5028
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5029
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5030
                        WHERE iid = ".$lp;
5031
                $res = Database::query($sql);
5032
5033
                return $res;
5034
            }
5035
        }
5036
5037
        return false;
5038
    }
5039
5040
    /**
5041
     * Sets the JS lib setting in the database directly.
5042
     * This is the JavaScript library file this lp needs to load on startup.
5043
     *
5044
     * @param string $lib Proximity setting
5045
     *
5046
     * @return bool True on update success. False otherwise.
5047
     */
5048
    public function set_jslib($lib = '')
5049
    {
5050
        $lp = $this->get_id();
5051
5052
        if ($lp != 0) {
5053
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5054
            $lib = Database::escape_string($lib);
5055
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5056
                    WHERE iid = $lp";
5057
            $res = Database::query($sql);
5058
5059
            return $res;
5060
        }
5061
5062
        return false;
5063
    }
5064
5065
    /**
5066
     * Sets the name of the LP maker (publisher) (and save).
5067
     *
5068
     * @param string $name Optional string giving the new content_maker of this learnpath
5069
     *
5070
     * @return bool True
5071
     */
5072
    public function set_maker($name = '')
5073
    {
5074
        if (empty($name)) {
5075
            return false;
5076
        }
5077
        $this->maker = $name;
5078
        $table = Database::get_course_table(TABLE_LP_MAIN);
5079
        $lp_id = $this->get_id();
5080
        $sql = "UPDATE $table SET
5081
                content_maker = '".Database::escape_string($this->maker)."'
5082
                WHERE iid = $lp_id";
5083
        Database::query($sql);
5084
5085
        return true;
5086
    }
5087
5088
    /**
5089
     * Sets the name of the current learnpath (and save).
5090
     *
5091
     * @param string $name Optional string giving the new name of this learnpath
5092
     *
5093
     * @return bool True/False
5094
     */
5095
    public function set_name($name = null)
5096
    {
5097
        if (empty($name)) {
5098
            return false;
5099
        }
5100
        $this->name = Database::escape_string($name);
5101
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5102
        $lp_id = $this->get_id();
5103
        $course_id = $this->course_info['real_id'];
5104
        $sql = "UPDATE $lp_table SET
5105
                name = '".Database::escape_string($this->name)."'
5106
                WHERE iid = $lp_id";
5107
        $result = Database::query($sql);
5108
        // If the lp is visible on the homepage, change his name there.
5109
        if (Database::affected_rows($result)) {
5110
            $session_id = api_get_session_id();
5111
            $session_condition = api_get_session_condition($session_id);
5112
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5113
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5114
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5115
            	    WHERE
5116
            	        c_id = $course_id AND
5117
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5118
            Database::query($sql);
5119
5120
            return true;
5121
        }
5122
5123
        return false;
5124
    }
5125
5126
    /**
5127
     * Set index specified prefix terms for all items in this path.
5128
     *
5129
     * @param string $terms_string Comma-separated list of terms
5130
     * @param string $prefix       Xapian term prefix
5131
     *
5132
     * @return bool False on error, true otherwise
5133
     */
5134
    public function set_terms_by_prefix($terms_string, $prefix)
5135
    {
5136
        $course_id = api_get_course_int_id();
5137
        if (api_get_setting('search_enabled') !== 'true') {
5138
            return false;
5139
        }
5140
5141
        if (!extension_loaded('xapian')) {
5142
            return false;
5143
        }
5144
5145
        $terms_string = trim($terms_string);
5146
        $terms = explode(',', $terms_string);
5147
        array_walk($terms, 'trim_value');
5148
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5149
5150
        // Don't do anything if no change, verify only at DB, not the search engine.
5151
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5152
            return false;
5153
        }
5154
5155
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5156
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5157
5158
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5159
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5160
        $lp_id = (int) $_POST['lp_id'];
5161
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5162
        $result = Database::query($sql);
5163
        $di = new ChamiloIndexer();
5164
5165
        while ($lp_item = Database::fetch_array($result)) {
5166
            // Get search_did.
5167
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5168
            $sql = 'SELECT * FROM %s
5169
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5170
                    LIMIT 1';
5171
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5172
5173
            //echo $sql; echo '<br>';
5174
            $res = Database::query($sql);
5175
            if (Database::num_rows($res) > 0) {
5176
                $se_ref = Database::fetch_array($res);
5177
                // Compare terms.
5178
                $doc = $di->get_document($se_ref['search_did']);
5179
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5180
                $xterms = [];
5181
                foreach ($xapian_terms as $xapian_term) {
5182
                    $xterms[] = substr($xapian_term['name'], 1);
5183
                }
5184
5185
                $dterms = $terms;
5186
                $missing_terms = array_diff($dterms, $xterms);
5187
                $deprecated_terms = array_diff($xterms, $dterms);
5188
5189
                // Save it to search engine.
5190
                foreach ($missing_terms as $term) {
5191
                    $doc->add_term($prefix.$term, 1);
5192
                }
5193
                foreach ($deprecated_terms as $term) {
5194
                    $doc->remove_term($prefix.$term);
5195
                }
5196
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5197
                $di->getDb()->flush();
5198
            }
5199
        }
5200
5201
        return true;
5202
    }
5203
5204
    /**
5205
     * Sets the theme of the LP (local/remote) (and save).
5206
     *
5207
     * @param string $name Optional string giving the new theme of this learnpath
5208
     *
5209
     * @return bool Returns true if theme name is not empty
5210
     */
5211
    public function set_theme($name = '')
5212
    {
5213
        $this->theme = $name;
5214
        $table = Database::get_course_table(TABLE_LP_MAIN);
5215
        $lp_id = $this->get_id();
5216
        $sql = "UPDATE $table
5217
                SET theme = '".Database::escape_string($this->theme)."'
5218
                WHERE iid = $lp_id";
5219
        Database::query($sql);
5220
5221
        return true;
5222
    }
5223
5224
    /**
5225
     * Sets the image of an LP (and save).
5226
     *
5227
     * @param string $name Optional string giving the new image of this learnpath
5228
     *
5229
     * @return bool Returns true if theme name is not empty
5230
     */
5231
    public function set_preview_image($name = '')
5232
    {
5233
        $this->preview_image = $name;
5234
        $table = Database::get_course_table(TABLE_LP_MAIN);
5235
        $lp_id = $this->get_id();
5236
        $sql = "UPDATE $table SET
5237
                preview_image = '".Database::escape_string($this->preview_image)."'
5238
                WHERE iid = $lp_id";
5239
        Database::query($sql);
5240
5241
        return true;
5242
    }
5243
5244
    /**
5245
     * Sets the author of a LP (and save).
5246
     *
5247
     * @param string $name Optional string giving the new author of this learnpath
5248
     *
5249
     * @return bool Returns true if author's name is not empty
5250
     */
5251
    public function set_author($name = '')
5252
    {
5253
        $this->author = $name;
5254
        $table = Database::get_course_table(TABLE_LP_MAIN);
5255
        $lp_id = $this->get_id();
5256
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5257
                WHERE iid = $lp_id";
5258
        Database::query($sql);
5259
5260
        return true;
5261
    }
5262
5263
    /**
5264
     * Sets the hide_toc_frame parameter of a LP (and save).
5265
     *
5266
     * @param int $hide 1 if frame is hidden 0 then else
5267
     *
5268
     * @return bool Returns true if author's name is not empty
5269
     */
5270
    public function set_hide_toc_frame($hide)
5271
    {
5272
        if (intval($hide) == $hide) {
5273
            $this->hide_toc_frame = $hide;
5274
            $table = Database::get_course_table(TABLE_LP_MAIN);
5275
            $lp_id = $this->get_id();
5276
            $sql = "UPDATE $table SET
5277
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5278
                    WHERE iid = $lp_id";
5279
            if ($this->debug > 2) {
5280
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5281
            }
5282
            Database::query($sql);
5283
5284
            return true;
5285
        }
5286
5287
        return false;
5288
    }
5289
5290
    /**
5291
     * Sets the prerequisite of a LP (and save).
5292
     *
5293
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5294
     *
5295
     * @return bool returns true if prerequisite is not empty
5296
     */
5297
    public function set_prerequisite($prerequisite)
5298
    {
5299
        $this->prerequisite = (int) $prerequisite;
5300
        $table = Database::get_course_table(TABLE_LP_MAIN);
5301
        $lp_id = $this->get_id();
5302
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5303
                WHERE iid = $lp_id";
5304
        Database::query($sql);
5305
5306
        return true;
5307
    }
5308
5309
    /**
5310
     * Sets the location/proximity of the LP (local/remote) (and save).
5311
     *
5312
     * @param string $name Optional string giving the new location of this learnpath
5313
     *
5314
     * @return bool True on success / False on error
5315
     */
5316
    public function set_proximity($name = '')
5317
    {
5318
        if (empty($name)) {
5319
            return false;
5320
        }
5321
5322
        $this->proximity = $name;
5323
        $table = Database::get_course_table(TABLE_LP_MAIN);
5324
        $lp_id = $this->get_id();
5325
        $sql = "UPDATE $table SET
5326
                    content_local = '".Database::escape_string($name)."'
5327
                WHERE iid = $lp_id";
5328
        Database::query($sql);
5329
5330
        return true;
5331
    }
5332
5333
    /**
5334
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5335
     *
5336
     * @param int $id DB ID of the item
5337
     */
5338
    public function set_previous_item($id)
5339
    {
5340
        if ($this->debug > 0) {
5341
            error_log('In learnpath::set_previous_item()', 0);
5342
        }
5343
        $this->last = $id;
5344
    }
5345
5346
    /**
5347
     * Sets use_max_score.
5348
     *
5349
     * @param int $use_max_score Optional string giving the new location of this learnpath
5350
     *
5351
     * @return bool True on success / False on error
5352
     */
5353
    public function set_use_max_score($use_max_score = 1)
5354
    {
5355
        $use_max_score = (int) $use_max_score;
5356
        $this->use_max_score = $use_max_score;
5357
        $table = Database::get_course_table(TABLE_LP_MAIN);
5358
        $lp_id = $this->get_id();
5359
        $sql = "UPDATE $table SET
5360
                    use_max_score = '".$this->use_max_score."'
5361
                WHERE iid = $lp_id";
5362
        Database::query($sql);
5363
5364
        return true;
5365
    }
5366
5367
    /**
5368
     * Sets and saves the expired_on date.
5369
     *
5370
     * @param string $expired_on Optional string giving the new author of this learnpath
5371
     *
5372
     * @throws \Doctrine\ORM\OptimisticLockException
5373
     *
5374
     * @return bool Returns true if author's name is not empty
5375
     */
5376
    public function set_expired_on($expired_on)
5377
    {
5378
        $em = Database::getManager();
5379
        /** @var CLp $lp */
5380
        $lp = $em
5381
            ->getRepository('ChamiloCourseBundle:CLp')
5382
            ->findOneBy(
5383
                [
5384
                    'iid' => $this->get_id(),
5385
                ]
5386
            );
5387
5388
        if (!$lp) {
5389
            return false;
5390
        }
5391
5392
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5393
5394
        $lp->setExpiredOn($this->expired_on);
5395
        $em->persist($lp);
5396
        $em->flush();
5397
5398
        return true;
5399
    }
5400
5401
    /**
5402
     * Sets and saves the publicated_on date.
5403
     *
5404
     * @param string $publicated_on Optional string giving the new author of this learnpath
5405
     *
5406
     * @throws \Doctrine\ORM\OptimisticLockException
5407
     *
5408
     * @return bool Returns true if author's name is not empty
5409
     */
5410
    public function set_publicated_on($publicated_on)
5411
    {
5412
        $em = Database::getManager();
5413
        /** @var CLp $lp */
5414
        $lp = $em
5415
            ->getRepository('ChamiloCourseBundle:CLp')
5416
            ->findOneBy(
5417
                [
5418
                    'iid' => $this->get_id(),
5419
                ]
5420
            );
5421
5422
        if (!$lp) {
5423
            return false;
5424
        }
5425
5426
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5427
        $lp->setPublicatedOn($this->publicated_on);
5428
        $em->persist($lp);
5429
        $em->flush();
5430
5431
        return true;
5432
    }
5433
5434
    /**
5435
     * Sets and saves the expired_on date.
5436
     *
5437
     * @return bool Returns true if author's name is not empty
5438
     */
5439
    public function set_modified_on()
5440
    {
5441
        $this->modified_on = api_get_utc_datetime();
5442
        $table = Database::get_course_table(TABLE_LP_MAIN);
5443
        $lp_id = $this->get_id();
5444
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5445
                WHERE iid = $lp_id";
5446
        Database::query($sql);
5447
5448
        return true;
5449
    }
5450
5451
    /**
5452
     * Sets the object's error message.
5453
     *
5454
     * @param string $error Error message. If empty, reinits the error string
5455
     */
5456
    public function set_error_msg($error = '')
5457
    {
5458
        if ($this->debug > 0) {
5459
            error_log('In learnpath::set_error_msg()', 0);
5460
        }
5461
        if (empty($error)) {
5462
            $this->error = '';
5463
        } else {
5464
            $this->error .= $error;
5465
        }
5466
    }
5467
5468
    /**
5469
     * Launches the current item if not 'sco'
5470
     * (starts timer and make sure there is a record ready in the DB).
5471
     *
5472
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5473
     *
5474
     * @return bool
5475
     */
5476
    public function start_current_item($allow_new_attempt = false)
5477
    {
5478
        $debug = $this->debug;
5479
        if ($debug) {
5480
            error_log('In learnpath::start_current_item()');
5481
            error_log('current: '.$this->current);
5482
        }
5483
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5484
            $type = $this->get_type();
5485
            $item_type = $this->items[$this->current]->get_type();
5486
            if (($type == 2 && $item_type != 'sco') ||
5487
                ($type == 3 && $item_type != 'au') ||
5488
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5489
            ) {
5490
                if ($debug) {
5491
                    error_log('item type: '.$item_type);
5492
                    error_log('lp type: '.$type);
5493
                }
5494
                $this->items[$this->current]->open($allow_new_attempt);
5495
                $this->autocomplete_parents($this->current);
5496
                $prereq_check = $this->prerequisites_match($this->current);
5497
                if ($debug) {
5498
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5499
                }
5500
                $this->items[$this->current]->save(false, $prereq_check);
5501
            }
5502
            // If sco, then it is supposed to have been updated by some other call.
5503
            if ($item_type == 'sco') {
5504
                $this->items[$this->current]->restart();
5505
            }
5506
        }
5507
        if ($debug) {
5508
            error_log('lp_view_session_id');
5509
            error_log($this->lp_view_session_id);
5510
            error_log('api session id');
5511
            error_log(api_get_session_id());
5512
            error_log('End of learnpath::start_current_item()');
5513
        }
5514
5515
        return true;
5516
    }
5517
5518
    /**
5519
     * Stops the processing and counters for the old item (as held in $this->last).
5520
     *
5521
     * @return bool True/False
5522
     */
5523
    public function stop_previous_item()
5524
    {
5525
        $debug = $this->debug;
5526
        if ($debug) {
5527
            error_log('In learnpath::stop_previous_item()', 0);
5528
        }
5529
5530
        if ($this->last != 0 && $this->last != $this->current &&
5531
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5532
        ) {
5533
            if ($debug) {
5534
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5535
            }
5536
            switch ($this->get_type()) {
5537
                case '3':
5538
                    if ($this->items[$this->last]->get_type() != 'au') {
5539
                        if ($debug) {
5540
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5541
                        }
5542
                        $this->items[$this->last]->close();
5543
                    } else {
5544
                        if ($debug) {
5545
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5546
                        }
5547
                    }
5548
                    break;
5549
                case '2':
5550
                    if ($this->items[$this->last]->get_type() != 'sco') {
5551
                        if ($debug) {
5552
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5553
                        }
5554
                        $this->items[$this->last]->close();
5555
                    } else {
5556
                        if ($debug) {
5557
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5558
                        }
5559
                    }
5560
                    break;
5561
                case '1':
5562
                default:
5563
                    if ($debug) {
5564
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5565
                    }
5566
                    $this->items[$this->last]->close();
5567
                    break;
5568
            }
5569
        } else {
5570
            if ($debug) {
5571
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5572
            }
5573
5574
            return false;
5575
        }
5576
5577
        return true;
5578
    }
5579
5580
    /**
5581
     * Updates the default view mode from fullscreen to embedded and inversely.
5582
     *
5583
     * @return string The current default view mode ('fullscreen' or 'embedded')
5584
     */
5585
    public function update_default_view_mode()
5586
    {
5587
        $table = Database::get_course_table(TABLE_LP_MAIN);
5588
        $sql = "SELECT * FROM $table
5589
                WHERE iid = ".$this->get_id();
5590
        $res = Database::query($sql);
5591
        if (Database::num_rows($res) > 0) {
5592
            $row = Database::fetch_array($res);
5593
            $default_view_mode = $row['default_view_mod'];
5594
            $view_mode = $default_view_mode;
5595
            switch ($default_view_mode) {
5596
                case 'fullscreen': // default with popup
5597
                    $view_mode = 'embedded';
5598
                    break;
5599
                case 'embedded': // default view with left menu
5600
                    $view_mode = 'embedframe';
5601
                    break;
5602
                case 'embedframe': //folded menu
5603
                    $view_mode = 'impress';
5604
                    break;
5605
                case 'impress':
5606
                    $view_mode = 'fullscreen';
5607
                    break;
5608
            }
5609
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5610
                    WHERE iid = ".$this->get_id();
5611
            Database::query($sql);
5612
            $this->mode = $view_mode;
5613
5614
            return $view_mode;
5615
        }
5616
5617
        return -1;
5618
    }
5619
5620
    /**
5621
     * Updates the default behaviour about auto-commiting SCORM updates.
5622
     *
5623
     * @return bool True if auto-commit has been set to 'on', false otherwise
5624
     */
5625
    public function update_default_scorm_commit()
5626
    {
5627
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5628
        $sql = "SELECT * FROM $lp_table
5629
                WHERE iid = ".$this->get_id();
5630
        $res = Database::query($sql);
5631
        if (Database::num_rows($res) > 0) {
5632
            $row = Database::fetch_array($res);
5633
            $force = $row['force_commit'];
5634
            if ($force == 1) {
5635
                $force = 0;
5636
                $force_return = false;
5637
            } elseif ($force == 0) {
5638
                $force = 1;
5639
                $force_return = true;
5640
            }
5641
            $sql = "UPDATE $lp_table SET force_commit = $force
5642
                    WHERE iid = ".$this->get_id();
5643
            Database::query($sql);
5644
            $this->force_commit = $force_return;
5645
5646
            return $force_return;
5647
        }
5648
5649
        return -1;
5650
    }
5651
5652
    /**
5653
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5654
     *
5655
     * @return bool True on success, false on failure
5656
     */
5657
    public function update_display_order()
5658
    {
5659
        $course_id = api_get_course_int_id();
5660
        $table = Database::get_course_table(TABLE_LP_MAIN);
5661
        $sql = "SELECT * FROM $table
5662
                WHERE c_id = $course_id
5663
                ORDER BY display_order";
5664
        $res = Database::query($sql);
5665
        if ($res === false) {
5666
            return false;
5667
        }
5668
5669
        $num = Database::num_rows($res);
5670
        // First check the order is correct, globally (might be wrong because
5671
        // of versions < 1.8.4).
5672
        if ($num > 0) {
5673
            $i = 1;
5674
            while ($row = Database::fetch_array($res)) {
5675
                if ($row['display_order'] != $i) {
5676
                    // If we find a gap in the order, we need to fix it.
5677
                    $sql = "UPDATE $table SET display_order = $i
5678
                            WHERE iid = ".$row['iid'];
5679
                    Database::query($sql);
5680
                }
5681
                $i++;
5682
            }
5683
        }
5684
5685
        return true;
5686
    }
5687
5688
    /**
5689
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5690
     *
5691
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5692
     */
5693
    public function update_reinit()
5694
    {
5695
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5696
        $sql = "SELECT * FROM $lp_table
5697
                WHERE iid = ".$this->get_id();
5698
        $res = Database::query($sql);
5699
        if (Database::num_rows($res) > 0) {
5700
            $row = Database::fetch_array($res);
5701
            $force = $row['prevent_reinit'];
5702
            if ($force == 1) {
5703
                $force = 0;
5704
            } elseif ($force == 0) {
5705
                $force = 1;
5706
            }
5707
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5708
                    WHERE iid = ".$this->get_id();
5709
            Database::query($sql);
5710
            $this->prevent_reinit = $force;
5711
5712
            return $force;
5713
        }
5714
5715
        return -1;
5716
    }
5717
5718
    /**
5719
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5720
     *
5721
     * @return string 'single', 'multi' or 'seriousgame'
5722
     *
5723
     * @author ndiechburg <[email protected]>
5724
     */
5725
    public function get_attempt_mode()
5726
    {
5727
        //Set default value for seriousgame_mode
5728
        if (!isset($this->seriousgame_mode)) {
5729
            $this->seriousgame_mode = 0;
5730
        }
5731
        // Set default value for prevent_reinit
5732
        if (!isset($this->prevent_reinit)) {
5733
            $this->prevent_reinit = 1;
5734
        }
5735
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5736
            return 'seriousgame';
5737
        }
5738
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5739
            return 'single';
5740
        }
5741
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5742
            return 'multiple';
5743
        }
5744
5745
        return 'single';
5746
    }
5747
5748
    /**
5749
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5750
     *
5751
     * @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...
5752
     *
5753
     * @return bool
5754
     *
5755
     * @author ndiechburg <[email protected]>
5756
     */
5757
    public function set_attempt_mode($mode)
5758
    {
5759
        switch ($mode) {
5760
            case 'seriousgame':
5761
                $sg_mode = 1;
5762
                $prevent_reinit = 1;
5763
                break;
5764
            case 'single':
5765
                $sg_mode = 0;
5766
                $prevent_reinit = 1;
5767
                break;
5768
            case 'multiple':
5769
                $sg_mode = 0;
5770
                $prevent_reinit = 0;
5771
                break;
5772
            default:
5773
                $sg_mode = 0;
5774
                $prevent_reinit = 0;
5775
                break;
5776
        }
5777
        $this->prevent_reinit = $prevent_reinit;
5778
        $this->seriousgame_mode = $sg_mode;
5779
        $table = Database::get_course_table(TABLE_LP_MAIN);
5780
        $sql = "UPDATE $table SET
5781
                prevent_reinit = $prevent_reinit ,
5782
                seriousgame_mode = $sg_mode
5783
                WHERE iid = ".$this->get_id();
5784
        $res = Database::query($sql);
5785
        if ($res) {
5786
            return true;
5787
        } else {
5788
            return false;
5789
        }
5790
    }
5791
5792
    /**
5793
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5794
     *
5795
     * @author ndiechburg <[email protected]>
5796
     */
5797
    public function switch_attempt_mode()
5798
    {
5799
        if ($this->debug > 0) {
5800
            error_log('In learnpath::switch_attempt_mode()', 0);
5801
        }
5802
        $mode = $this->get_attempt_mode();
5803
        switch ($mode) {
5804
            case 'single':
5805
                $next_mode = 'multiple';
5806
                break;
5807
            case 'multiple':
5808
                $next_mode = 'seriousgame';
5809
                break;
5810
            case 'seriousgame':
5811
                $next_mode = 'single';
5812
                break;
5813
            default:
5814
                $next_mode = 'single';
5815
                break;
5816
        }
5817
        $this->set_attempt_mode($next_mode);
5818
    }
5819
5820
    /**
5821
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5822
     * but possibility to do again a completed item.
5823
     *
5824
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5825
     *
5826
     * @author ndiechburg <[email protected]>
5827
     */
5828
    public function set_seriousgame_mode()
5829
    {
5830
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5831
        $sql = "SELECT * FROM $lp_table
5832
                WHERE iid = ".$this->get_id();
5833
        $res = Database::query($sql);
5834
        if (Database::num_rows($res) > 0) {
5835
            $row = Database::fetch_array($res);
5836
            $force = $row['seriousgame_mode'];
5837
            if ($force == 1) {
5838
                $force = 0;
5839
            } elseif ($force == 0) {
5840
                $force = 1;
5841
            }
5842
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5843
			        WHERE iid = ".$this->get_id();
5844
            Database::query($sql);
5845
            $this->seriousgame_mode = $force;
5846
5847
            return $force;
5848
        }
5849
5850
        return -1;
5851
    }
5852
5853
    /**
5854
     * Updates the "scorm_debug" value that shows or hide the debug window.
5855
     *
5856
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5857
     */
5858
    public function update_scorm_debug()
5859
    {
5860
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5861
        $sql = "SELECT * FROM $lp_table
5862
                WHERE iid = ".$this->get_id();
5863
        $res = Database::query($sql);
5864
        if (Database::num_rows($res) > 0) {
5865
            $row = Database::fetch_array($res);
5866
            $force = $row['debug'];
5867
            if ($force == 1) {
5868
                $force = 0;
5869
            } elseif ($force == 0) {
5870
                $force = 1;
5871
            }
5872
            $sql = "UPDATE $lp_table SET debug = $force
5873
                    WHERE iid = ".$this->get_id();
5874
            Database::query($sql);
5875
            $this->scorm_debug = $force;
5876
5877
            return $force;
5878
        }
5879
5880
        return -1;
5881
    }
5882
5883
    /**
5884
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5885
     *
5886
     * @author Kevin Van Den Haute
5887
     *
5888
     * @param  array
5889
     */
5890
    public function tree_array($array)
5891
    {
5892
        $array = $this->sort_tree_array($array);
5893
        $this->create_tree_array($array);
5894
    }
5895
5896
    /**
5897
     * Creates an array with the elements of the learning path tree in it.
5898
     *
5899
     * @author Kevin Van Den Haute
5900
     *
5901
     * @param array $array
5902
     * @param int   $parent
5903
     * @param int   $depth
5904
     * @param array $tmp
5905
     */
5906
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5907
    {
5908
        if (is_array($array)) {
5909
            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...
5910
                if ($array[$i]['parent_item_id'] == $parent) {
5911
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5912
                        $tmp[] = $array[$i]['parent_item_id'];
5913
                        $depth++;
5914
                    }
5915
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5916
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5917
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5918
5919
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5920
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5921
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5922
                    $this->arrMenu[] = [
5923
                        'id' => $array[$i]['id'],
5924
                        'ref' => $ref,
5925
                        'item_type' => $array[$i]['item_type'],
5926
                        'title' => $array[$i]['title'],
5927
                        'path' => $path,
5928
                        'description' => $array[$i]['description'],
5929
                        'parent_item_id' => $array[$i]['parent_item_id'],
5930
                        'previous_item_id' => $array[$i]['previous_item_id'],
5931
                        'next_item_id' => $array[$i]['next_item_id'],
5932
                        'min_score' => $array[$i]['min_score'],
5933
                        'max_score' => $array[$i]['max_score'],
5934
                        'mastery_score' => $array[$i]['mastery_score'],
5935
                        'display_order' => $array[$i]['display_order'],
5936
                        'prerequisite' => $preq,
5937
                        'depth' => $depth,
5938
                        'audio' => $audio,
5939
                        'prerequisite_min_score' => $prerequisiteMinScore,
5940
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5941
                    ];
5942
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5943
                }
5944
            }
5945
        }
5946
    }
5947
5948
    /**
5949
     * Sorts a multi dimensional array by parent id and display order.
5950
     *
5951
     * @author Kevin Van Den Haute
5952
     *
5953
     * @param array $array (array with al the learning path items in it)
5954
     *
5955
     * @return array
5956
     */
5957
    public function sort_tree_array($array)
5958
    {
5959
        foreach ($array as $key => $row) {
5960
            $parent[$key] = $row['parent_item_id'];
5961
            $position[$key] = $row['display_order'];
5962
        }
5963
5964
        if (count($array) > 0) {
5965
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5966
        }
5967
5968
        return $array;
5969
    }
5970
5971
    /**
5972
     * Function that creates a html list of learning path items so that we can add audio files to them.
5973
     *
5974
     * @author Kevin Van Den Haute
5975
     *
5976
     * @return string
5977
     */
5978
    public function overview()
5979
    {
5980
        $return = '';
5981
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5982
5983
        // we need to start a form when we want to update all the mp3 files
5984
        if ($update_audio == 'true') {
5985
            $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">';
5986
        }
5987
        $return .= '<div id="message"></div>';
5988
        if (count($this->items) == 0) {
5989
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5990
        } else {
5991
            $return_audio = '<table class="data_table">';
5992
            $return_audio .= '<tr>';
5993
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5994
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5995
            $return_audio .= '</tr>';
5996
5997
            if ($update_audio != 'true') {
5998
                $return .= '<div class="col-md-12">';
5999
                $return .= self::return_new_tree($update_audio);
6000
                $return .= '</div>';
6001
                $return .= Display::div(
6002
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6003
                    ['style' => 'float:left; margin-top:15px;width:100%']
6004
                );
6005
            } else {
6006
                $return_audio .= self::return_new_tree($update_audio);
6007
                $return .= $return_audio.'</table>';
6008
            }
6009
6010
            // We need to close the form when we are updating the mp3 files.
6011
            if ($update_audio == 'true') {
6012
                $return .= '<div class="footer-audio">';
6013
                $return .= Display::button(
6014
                    'save_audio',
6015
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6016
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6017
                );
6018
                $return .= '</div>';
6019
            }
6020
        }
6021
6022
        // We need to close the form when we are updating the mp3 files.
6023
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6024
            $return .= '</form>';
6025
        }
6026
6027
        return $return;
6028
    }
6029
6030
    /**
6031
     * @param string $update_audio
6032
     *
6033
     * @return array
6034
     */
6035
    public function processBuildMenuElements($update_audio = 'false')
6036
    {
6037
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6038
        $course_id = api_get_course_int_id();
6039
        $table = Database::get_course_table(TABLE_LP_ITEM);
6040
6041
        $sql = "SELECT * FROM $table
6042
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6043
6044
        $result = Database::query($sql);
6045
        $arrLP = [];
6046
        while ($row = Database::fetch_array($result)) {
6047
            $arrLP[] = [
6048
                'id' => $row['iid'],
6049
                'item_type' => $row['item_type'],
6050
                'title' => Security::remove_XSS($row['title']),
6051
                'path' => $row['path'],
6052
                'description' => Security::remove_XSS($row['description']),
6053
                'parent_item_id' => $row['parent_item_id'],
6054
                'previous_item_id' => $row['previous_item_id'],
6055
                'next_item_id' => $row['next_item_id'],
6056
                'max_score' => $row['max_score'],
6057
                'min_score' => $row['min_score'],
6058
                'mastery_score' => $row['mastery_score'],
6059
                'prerequisite' => $row['prerequisite'],
6060
                'display_order' => $row['display_order'],
6061
                'audio' => $row['audio'],
6062
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6063
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6064
            ];
6065
        }
6066
6067
        $this->tree_array($arrLP);
6068
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6069
        unset($this->arrMenu);
6070
        $default_data = null;
6071
        $default_content = null;
6072
        $elements = [];
6073
        $return_audio = null;
6074
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6075
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6076
        $countItems = count($arrLP);
6077
6078
        $upIcon = Display::return_icon(
6079
            'up.png',
6080
            get_lang('Up'),
6081
            [],
6082
            ICON_SIZE_TINY
6083
        );
6084
6085
        $disableUpIcon = Display::return_icon(
6086
            'up_na.png',
6087
            get_lang('Up'),
6088
            [],
6089
            ICON_SIZE_TINY
6090
        );
6091
6092
        $downIcon = Display::return_icon(
6093
            'down.png',
6094
            get_lang('Down'),
6095
            [],
6096
            ICON_SIZE_TINY
6097
        );
6098
6099
        $disableDownIcon = Display::return_icon(
6100
            'down_na.png',
6101
            get_lang('Down'),
6102
            [],
6103
            ICON_SIZE_TINY
6104
        );
6105
        for ($i = 0; $i < $countItems; $i++) {
6106
            $parent_id = $arrLP[$i]['parent_item_id'];
6107
            $title = $arrLP[$i]['title'];
6108
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6109
            // Link for the documents
6110
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6111
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6112
                $title_cut = Display::url(
6113
                    $title_cut,
6114
                    $url,
6115
                    [
6116
                        'class' => 'ajax moved',
6117
                        'data-title' => $title,
6118
                        'title' => $title,
6119
                    ]
6120
                );
6121
            }
6122
6123
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6124
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6125
                Session::write('pathItem', $arrLP[$i]['path']);
6126
            }
6127
6128
            $oddClass = 'row_even';
6129
            if (($i % 2) == 0) {
6130
                $oddClass = 'row_odd';
6131
            }
6132
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6133
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6134
6135
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6136
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6137
            } else {
6138
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6139
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6140
                } else {
6141
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6142
                        $icon = Display::return_icon('certificate.png');
6143
                    } else {
6144
                        $icon = Display::return_icon('folder_document.png');
6145
                    }
6146
                }
6147
            }
6148
6149
            // The audio column.
6150
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6151
            $audio = '';
6152
            if (!$update_audio || $update_audio != 'true') {
6153
                if (empty($arrLP[$i]['audio'])) {
6154
                    $audio .= '';
6155
                }
6156
            } else {
6157
                $types = self::getChapterTypes();
6158
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6159
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6160
                    if (!empty($arrLP[$i]['audio'])) {
6161
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6162
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6163
                    }
6164
                }
6165
            }
6166
6167
            $return_audio .= Display::span($icon.' '.$title).
6168
                Display::tag(
6169
                    'td',
6170
                    $audio,
6171
                    ['style' => '']
6172
                );
6173
            $return_audio .= '</td>';
6174
            $move_icon = '';
6175
            $move_item_icon = '';
6176
            $edit_icon = '';
6177
            $delete_icon = '';
6178
            $audio_icon = '';
6179
            $prerequisities_icon = '';
6180
            $forumIcon = '';
6181
            $previewIcon = '';
6182
            $pluginCalendarIcon = '';
6183
            $orderIcons = '';
6184
6185
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6186
            $plugin = null;
6187
            if ($pluginCalendar) {
6188
                $plugin = LearningCalendarPlugin::create();
6189
            }
6190
6191
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6192
6193
            if ($is_allowed_to_edit) {
6194
                if (!$update_audio || $update_audio != 'true') {
6195
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6196
                        $move_icon .= '<a class="moved" href="#">';
6197
                        $move_icon .= Display::return_icon(
6198
                            'move_everywhere.png',
6199
                            get_lang('Move'),
6200
                            [],
6201
                            ICON_SIZE_TINY
6202
                        );
6203
                        $move_icon .= '</a>';
6204
                    }
6205
                }
6206
6207
                // No edit for this item types
6208
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6209
                    if ($arrLP[$i]['item_type'] != 'dir') {
6210
                        $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">';
6211
                        $edit_icon .= Display::return_icon(
6212
                            'edit.png',
6213
                            get_lang('LearnpathEditModule'),
6214
                            [],
6215
                            ICON_SIZE_TINY
6216
                        );
6217
                        $edit_icon .= '</a>';
6218
6219
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6220
                            $forumThread = null;
6221
                            if (isset($this->items[$arrLP[$i]['id']])) {
6222
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6223
                                    $this->course_int_id,
6224
                                    $this->lp_session_id
6225
                                );
6226
                            }
6227
                            if ($forumThread) {
6228
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6229
                                        'action' => 'dissociate_forum',
6230
                                        'id' => $arrLP[$i]['id'],
6231
                                        'lp_id' => $this->lp_id,
6232
                                    ]);
6233
                                $forumIcon = Display::url(
6234
                                    Display::return_icon(
6235
                                        'forum.png',
6236
                                        get_lang('DissociateForumToLPItem'),
6237
                                        [],
6238
                                        ICON_SIZE_TINY
6239
                                    ),
6240
                                    $forumIconUrl,
6241
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6242
                                );
6243
                            } else {
6244
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6245
                                        'action' => 'create_forum',
6246
                                        'id' => $arrLP[$i]['id'],
6247
                                        'lp_id' => $this->lp_id,
6248
                                    ]);
6249
                                $forumIcon = Display::url(
6250
                                    Display::return_icon(
6251
                                        'forum.png',
6252
                                        get_lang('AssociateForumToLPItem'),
6253
                                        [],
6254
                                        ICON_SIZE_TINY
6255
                                    ),
6256
                                    $forumIconUrl,
6257
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6258
                                );
6259
                            }
6260
                        }
6261
                    } else {
6262
                        $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">';
6263
                        $edit_icon .= Display::return_icon(
6264
                            'edit.png',
6265
                            get_lang('LearnpathEditModule'),
6266
                            [],
6267
                            ICON_SIZE_TINY
6268
                        );
6269
                        $edit_icon .= '</a>';
6270
                    }
6271
                } else {
6272
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6273
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6274
                        $edit_icon .= Display::return_icon(
6275
                            'edit.png',
6276
                            get_lang('Edit'),
6277
                            [],
6278
                            ICON_SIZE_TINY
6279
                        );
6280
                        $edit_icon .= '</a>';
6281
                    }
6282
                }
6283
6284
                if ($pluginCalendar) {
6285
                    $pluginLink = $pluginUrl.
6286
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6287
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6288
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6289
                    if ($itemInfo && $itemInfo['value'] == 1) {
6290
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6291
                    }
6292
                    $pluginCalendarIcon = Display::url(
6293
                        $iconCalendar,
6294
                        $pluginLink,
6295
                        ['class' => 'btn btn-default']
6296
                    );
6297
                }
6298
6299
                if ($arrLP[$i]['item_type'] != 'final_item') {
6300
                    /*if ($isFirst) {
6301
                        $orderIcons = Display::url($disableUpIcon, '#', ['class' => 'btn btn-default']);
6302
                    } else {
6303
                    }*/
6304
                    $orderIcons = Display::url(
6305
                        $upIcon,
6306
                        'javascript:void(0)',
6307
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6308
                    );
6309
6310
                    /*if ($isLast) {
6311
                        $orderIcons .= Display::url($disableDownIcon, '#', ['class' => 'btn btn-default']);
6312
                    } else {
6313
6314
                    }*/
6315
                    $orderIcons .= Display::url(
6316
                        $downIcon,
6317
                        'javascript:void(0)',
6318
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6319
                    );
6320
                }
6321
6322
                $delete_icon .= ' <a 
6323
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6324
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6325
                    class="btn btn-default">';
6326
                $delete_icon .= Display::return_icon(
6327
                    'delete.png',
6328
                    get_lang('LearnpathDeleteModule'),
6329
                    [],
6330
                    ICON_SIZE_TINY
6331
                );
6332
                $delete_icon .= '</a>';
6333
6334
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6335
                $previewImage = Display::return_icon(
6336
                    'preview_view.png',
6337
                    get_lang('Preview'),
6338
                    [],
6339
                    ICON_SIZE_TINY
6340
                );
6341
6342
                switch ($arrLP[$i]['item_type']) {
6343
                    case TOOL_DOCUMENT:
6344
                    case TOOL_LP_FINAL_ITEM:
6345
                    case TOOL_READOUT_TEXT:
6346
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6347
                        $previewIcon = Display::url(
6348
                            $previewImage,
6349
                            $urlPreviewLink,
6350
                            [
6351
                                'target' => '_blank',
6352
                                'class' => 'btn btn-default',
6353
                                'data-title' => $arrLP[$i]['title'],
6354
                                'title' => $arrLP[$i]['title'],
6355
                            ]
6356
                        );
6357
                        break;
6358
                    case TOOL_THREAD:
6359
                    case TOOL_FORUM:
6360
                    case TOOL_QUIZ:
6361
                    case TOOL_STUDENTPUBLICATION:
6362
                    case TOOL_LP_FINAL_ITEM:
6363
                    case TOOL_LINK:
6364
                        //$target = '';
6365
                        //$class = 'btn btn-default ajax';
6366
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6367
                        $class = 'btn btn-default';
6368
                        $target = '_blank';
6369
                        //}
6370
6371
                        $link = self::rl_get_resource_link_for_learnpath(
6372
                            $this->course_int_id,
6373
                            $this->lp_id,
6374
                            $arrLP[$i]['id'],
6375
                            0
6376
                        );
6377
                        $previewIcon = Display::url(
6378
                            $previewImage,
6379
                            $link,
6380
                            [
6381
                                'class' => $class,
6382
                                'data-title' => $arrLP[$i]['title'],
6383
                                'title' => $arrLP[$i]['title'],
6384
                                'target' => $target,
6385
                            ]
6386
                        );
6387
                        break;
6388
                    default:
6389
                        $previewIcon = Display::url(
6390
                            $previewImage,
6391
                            $url.'&action=view_item',
6392
                            ['class' => 'btn btn-default', 'target' => '_blank']
6393
                        );
6394
                        break;
6395
                }
6396
6397
                if ($arrLP[$i]['item_type'] != 'dir') {
6398
                    $prerequisities_icon = Display::url(
6399
                        Display::return_icon(
6400
                            'accept.png',
6401
                            get_lang('LearnpathPrerequisites'),
6402
                            [],
6403
                            ICON_SIZE_TINY
6404
                        ),
6405
                        $url.'&action=edit_item_prereq',
6406
                        ['class' => 'btn btn-default']
6407
                    );
6408
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6409
                        $move_item_icon = Display::url(
6410
                            Display::return_icon(
6411
                                'move.png',
6412
                                get_lang('Move'),
6413
                                [],
6414
                                ICON_SIZE_TINY
6415
                            ),
6416
                            $url.'&action=move_item',
6417
                            ['class' => 'btn btn-default']
6418
                        );
6419
                    }
6420
                    $audio_icon = Display::url(
6421
                        Display::return_icon(
6422
                            'audio.png',
6423
                            get_lang('UplUpload'),
6424
                            [],
6425
                            ICON_SIZE_TINY
6426
                        ),
6427
                        $url.'&action=add_audio',
6428
                        ['class' => 'btn btn-default']
6429
                    );
6430
                }
6431
            }
6432
            if ($update_audio != 'true') {
6433
                $row = $move_icon.' '.$icon.
6434
                    Display::span($title_cut).
6435
                    Display::tag(
6436
                        'div',
6437
                        "<div class=\"btn-group btn-group-xs\">
6438
                                    $previewIcon 
6439
                                    $audio 
6440
                                    $edit_icon 
6441
                                    $pluginCalendarIcon
6442
                                    $forumIcon 
6443
                                    $prerequisities_icon 
6444
                                    $move_item_icon 
6445
                                    $audio_icon 
6446
                                    $orderIcons
6447
                                    $delete_icon
6448
                                </div>",
6449
                        ['class' => 'btn-toolbar button_actions']
6450
                    );
6451
            } else {
6452
                $row =
6453
                    Display::span($title.$icon).
6454
                    Display::span($audio, ['class' => 'button_actions']);
6455
            }
6456
6457
            $default_data[$arrLP[$i]['id']] = $row;
6458
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6459
6460
            if (empty($parent_id)) {
6461
                $elements[$arrLP[$i]['id']]['data'] = $row;
6462
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6463
            } else {
6464
                $parent_arrays = [];
6465
                if ($arrLP[$i]['depth'] > 1) {
6466
                    //Getting list of parents
6467
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6468
                        foreach ($arrLP as $item) {
6469
                            if ($item['id'] == $parent_id) {
6470
                                if ($item['parent_item_id'] == 0) {
6471
                                    $parent_id = $item['id'];
6472
                                    break;
6473
                                } else {
6474
                                    $parent_id = $item['parent_item_id'];
6475
                                    if (empty($parent_arrays)) {
6476
                                        $parent_arrays[] = intval($item['id']);
6477
                                    }
6478
                                    $parent_arrays[] = $parent_id;
6479
                                    break;
6480
                                }
6481
                            }
6482
                        }
6483
                    }
6484
                }
6485
6486
                if (!empty($parent_arrays)) {
6487
                    $parent_arrays = array_reverse($parent_arrays);
6488
                    $val = '$elements';
6489
                    $x = 0;
6490
                    foreach ($parent_arrays as $item) {
6491
                        if ($x != count($parent_arrays) - 1) {
6492
                            $val .= '["'.$item.'"]["children"]';
6493
                        } else {
6494
                            $val .= '["'.$item.'"]["children"]';
6495
                        }
6496
                        $x++;
6497
                    }
6498
                    $val .= "";
6499
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6500
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6501
                } else {
6502
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6503
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6504
                }
6505
            }
6506
        }
6507
6508
        return [
6509
            'elements' => $elements,
6510
            'default_data' => $default_data,
6511
            'default_content' => $default_content,
6512
            'return_audio' => $return_audio,
6513
        ];
6514
    }
6515
6516
    /**
6517
     * @param string $updateAudio true/false strings
6518
     *
6519
     * @return string
6520
     */
6521
    public function returnLpItemList($updateAudio)
6522
    {
6523
        $result = $this->processBuildMenuElements($updateAudio);
6524
6525
        $html = self::print_recursive(
6526
            $result['elements'],
6527
            $result['default_data'],
6528
            $result['default_content']
6529
        );
6530
6531
        if (!empty($html)) {
6532
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6533
        }
6534
6535
        return $html;
6536
    }
6537
6538
    /**
6539
     * @param string $update_audio
6540
     * @param bool   $drop_element_here
6541
     *
6542
     * @return string
6543
     */
6544
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6545
    {
6546
        $return = '';
6547
        $result = $this->processBuildMenuElements($update_audio);
6548
6549
        $list = '<ul id="lp_item_list">';
6550
        $tree = $this->print_recursive(
6551
            $result['elements'],
6552
            $result['default_data'],
6553
            $result['default_content']
6554
        );
6555
6556
        if (!empty($tree)) {
6557
            $list .= $tree;
6558
        } else {
6559
            if ($drop_element_here) {
6560
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6561
            }
6562
        }
6563
        $list .= '</ul>';
6564
6565
        $return .= Display::panelCollapse(
6566
            $this->name,
6567
            $list,
6568
            'scorm-list',
6569
            null,
6570
            'scorm-list-accordion',
6571
            'scorm-list-collapse'
6572
        );
6573
6574
        if ($update_audio === 'true') {
6575
            $return = $result['return_audio'];
6576
        }
6577
6578
        return $return;
6579
    }
6580
6581
    /**
6582
     * @param array $elements
6583
     * @param array $default_data
6584
     * @param array $default_content
6585
     *
6586
     * @return string
6587
     */
6588
    public function print_recursive($elements, $default_data, $default_content)
6589
    {
6590
        $return = '';
6591
        foreach ($elements as $key => $item) {
6592
            if (isset($item['load_data']) || empty($item['data'])) {
6593
                $item['data'] = $default_data[$item['load_data']];
6594
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6595
            }
6596
            $sub_list = '';
6597
            if (isset($item['type']) && $item['type'] == 'dir') {
6598
                // empty value
6599
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6600
            }
6601
            if (empty($item['children'])) {
6602
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6603
                $active = null;
6604
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6605
                    $active = 'active';
6606
                }
6607
                $return .= Display::tag(
6608
                    'li',
6609
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6610
                    ['id' => $key, 'class' => 'record li_container']
6611
                );
6612
            } else {
6613
                // Sections
6614
                $data = '';
6615
                if (isset($item['children'])) {
6616
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6617
                }
6618
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6619
                $return .= Display::tag(
6620
                    'li',
6621
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6622
                    ['id' => $key, 'class' => 'record li_container']
6623
                );
6624
            }
6625
        }
6626
6627
        return $return;
6628
    }
6629
6630
    /**
6631
     * This function builds the action menu.
6632
     *
6633
     * @param bool $returnContent          Optional
6634
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6635
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6636
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6637
     *
6638
     * @return string
6639
     */
6640
    public function build_action_menu(
6641
        $returnContent = false,
6642
        $showRequirementButtons = true,
6643
        $isConfigPage = false,
6644
        $allowExpand = true
6645
    ) {
6646
        $actionsLeft = '';
6647
        $actionsRight = '';
6648
        $actionsLeft .= Display::url(
6649
            Display::return_icon(
6650
                'back.png',
6651
                get_lang('ReturnToLearningPaths'),
6652
                '',
6653
                ICON_SIZE_MEDIUM
6654
            ),
6655
            'lp_controller.php?'.api_get_cidreq()
6656
        );
6657
        $actionsLeft .= Display::url(
6658
            Display::return_icon(
6659
                'preview_view.png',
6660
                get_lang('Preview'),
6661
                '',
6662
                ICON_SIZE_MEDIUM
6663
            ),
6664
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6665
                'action' => 'view',
6666
                'lp_id' => $this->lp_id,
6667
                'isStudentView' => 'true',
6668
            ])
6669
        );
6670
6671
        $actionsLeft .= Display::url(
6672
            Display::return_icon(
6673
                'upload_audio.png',
6674
                get_lang('UpdateAllAudioFragments'),
6675
                '',
6676
                ICON_SIZE_MEDIUM
6677
            ),
6678
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6679
                'action' => 'admin_view',
6680
                'lp_id' => $this->lp_id,
6681
                'updateaudio' => 'true',
6682
            ])
6683
        );
6684
6685
        if (!$isConfigPage) {
6686
            $actionsLeft .= Display::url(
6687
                Display::return_icon(
6688
                    'settings.png',
6689
                    get_lang('CourseSettings'),
6690
                    '',
6691
                    ICON_SIZE_MEDIUM
6692
                ),
6693
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6694
                    'action' => 'edit',
6695
                    'lp_id' => $this->lp_id,
6696
                ])
6697
            );
6698
        } else {
6699
            $actionsLeft .= Display::url(
6700
                Display::return_icon(
6701
                    'edit.png',
6702
                    get_lang('Edit'),
6703
                    '',
6704
                    ICON_SIZE_MEDIUM
6705
                ),
6706
                'lp_controller.php?'.http_build_query([
6707
                    'action' => 'build',
6708
                    'lp_id' => $this->lp_id,
6709
                ]).'&'.api_get_cidreq()
6710
            );
6711
        }
6712
6713
        if ($allowExpand) {
6714
            $actionsLeft .= Display::url(
6715
                Display::return_icon(
6716
                    'expand.png',
6717
                    get_lang('Expand'),
6718
                    ['id' => 'expand'],
6719
                    ICON_SIZE_MEDIUM
6720
                ).
6721
                Display::return_icon(
6722
                    'contract.png',
6723
                    get_lang('Collapse'),
6724
                    ['id' => 'contract', 'class' => 'hide'],
6725
                    ICON_SIZE_MEDIUM
6726
                ),
6727
                '#',
6728
                ['role' => 'button', 'id' => 'hide_bar_template']
6729
            );
6730
        }
6731
6732
        if ($showRequirementButtons) {
6733
            $buttons = [
6734
                [
6735
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6736
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6737
                        'action' => 'set_previous_step_as_prerequisite',
6738
                        'lp_id' => $this->lp_id,
6739
                    ]),
6740
                ],
6741
                [
6742
                    'title' => get_lang('ClearAllPrerequisites'),
6743
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6744
                        'action' => 'clear_prerequisites',
6745
                        'lp_id' => $this->lp_id,
6746
                    ]),
6747
                ],
6748
            ];
6749
            $actionsRight = Display::groupButtonWithDropDown(
6750
                get_lang('PrerequisitesOptions'),
6751
                $buttons,
6752
                true
6753
            );
6754
        }
6755
6756
        $toolbar = Display::toolbarAction(
6757
            'actions-lp-controller',
6758
            [$actionsLeft, $actionsRight]
6759
        );
6760
6761
        if ($returnContent) {
6762
            return $toolbar;
6763
        }
6764
6765
        echo $toolbar;
6766
    }
6767
6768
    /**
6769
     * Creates the default learning path folder.
6770
     *
6771
     * @param array $course
6772
     * @param int   $creatorId
6773
     *
6774
     * @return bool
6775
     */
6776
    public static function generate_learning_path_folder($course, $creatorId = 0)
6777
    {
6778
        // Creating learning_path folder
6779
        $dir = '/learning_path';
6780
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6781
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6782
6783
        $folder = false;
6784
        $folderData = create_unexisting_directory(
6785
            $course,
6786
            $creatorId,
6787
            0,
6788
            null,
6789
            0,
6790
            $filepath,
6791
            $dir,
6792
            get_lang('LearningPaths'),
6793
            0
6794
        );
6795
6796
        if (!empty($folderData)) {
6797
            $folder = true;
6798
        }
6799
6800
        return $folder;
6801
    }
6802
6803
    /**
6804
     * @param array  $course
6805
     * @param string $lp_name
6806
     * @param int    $creatorId
6807
     *
6808
     * @return array
6809
     */
6810
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6811
    {
6812
        $filepath = '';
6813
        $dir = '/learning_path/';
6814
6815
        if (empty($lp_name)) {
6816
            $lp_name = $this->name;
6817
        }
6818
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6819
        $folder = self::generate_learning_path_folder($course, $creatorId);
6820
6821
        // Limits title size
6822
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6823
        $dir = $dir.$title;
6824
6825
        // Creating LP folder
6826
        $documentId = null;
6827
        if ($folder) {
6828
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6829
            $folderData = create_unexisting_directory(
6830
                $course,
6831
                $creatorId,
6832
                0,
6833
                0,
6834
                0,
6835
                $filepath,
6836
                $dir,
6837
                $lp_name
6838
            );
6839
            if (!empty($folderData)) {
6840
                $folder = true;
6841
            }
6842
6843
            $documentId = $folderData->getIid();
6844
            $dir = $dir.'/';
6845
            if ($folder) {
6846
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6847
            }
6848
        }
6849
6850
        $array = [
6851
            'dir' => $dir,
6852
            'filepath' => $filepath,
6853
            'folder' => $folder,
6854
            'id' => $documentId,
6855
        ];
6856
6857
        return $array;
6858
    }
6859
6860
    /**
6861
     * Create a new document //still needs some finetuning.
6862
     *
6863
     * @param array  $courseInfo
6864
     * @param string $content
6865
     * @param string $title
6866
     * @param string $extension
6867
     * @param int    $parentId
6868
     * @param int    $creatorId  creator id
6869
     *
6870
     * @return int
6871
     */
6872
    public function create_document(
6873
        $courseInfo,
6874
        $content = '',
6875
        $title = '',
6876
        $extension = 'html',
6877
        $parentId = 0,
6878
        $creatorId = 0
6879
    ) {
6880
        if (!empty($courseInfo)) {
6881
            $course_id = $courseInfo['real_id'];
6882
        } else {
6883
            $course_id = api_get_course_int_id();
6884
        }
6885
6886
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6887
        $sessionId = api_get_session_id();
6888
6889
        // Generates folder
6890
        $result = $this->generate_lp_folder($courseInfo);
6891
        $dir = $result['dir'];
6892
6893
        if (empty($parentId) || $parentId == '/') {
6894
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6895
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6896
6897
            if ($parentId === '/') {
6898
                $dir = '/';
6899
            }
6900
6901
            // Please, do not modify this dirname formatting.
6902
            if (strstr($dir, '..')) {
6903
                $dir = '/';
6904
            }
6905
6906
            if (!empty($dir[0]) && $dir[0] == '.') {
6907
                $dir = substr($dir, 1);
6908
            }
6909
            if (!empty($dir[0]) && $dir[0] != '/') {
6910
                $dir = '/'.$dir;
6911
            }
6912
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6913
                $dir .= '/';
6914
            }
6915
        } else {
6916
            $parentInfo = DocumentManager::get_document_data_by_id(
6917
                $parentId,
6918
                $courseInfo['code']
6919
            );
6920
            if (!empty($parentInfo)) {
6921
                $dir = $parentInfo['path'].'/';
6922
            }
6923
        }
6924
6925
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6926
        if (!is_dir($filepath)) {
6927
            $dir = '/';
6928
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6929
        }
6930
6931
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6932
        // is already escaped twice when it gets here.
6933
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6934
        if (!empty($title)) {
6935
            $title = api_replace_dangerous_char(stripslashes($title));
6936
        } else {
6937
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6938
        }
6939
6940
        $title = disable_dangerous_file($title);
6941
        $filename = $title;
6942
        $content = !empty($content) ? $content : $_POST['content_lp'];
6943
        $tmp_filename = $filename;
6944
6945
        $i = 0;
6946
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6947
            $tmp_filename = $filename.'_'.++$i;
6948
        }
6949
6950
        $filename = $tmp_filename.'.'.$extension;
6951
        if ($extension == 'html') {
6952
            $content = stripslashes($content);
6953
            $content = str_replace(
6954
                api_get_path(WEB_COURSE_PATH),
6955
                api_get_path(REL_PATH).'courses/',
6956
                $content
6957
            );
6958
6959
            // Change the path of mp3 to absolute.
6960
            // The first regexp deals with :// urls.
6961
            $content = preg_replace(
6962
                "|(flashvars=\"file=)([^:/]+)/|",
6963
                "$1".api_get_path(
6964
                    REL_COURSE_PATH
6965
                ).$courseInfo['path'].'/document/',
6966
                $content
6967
            );
6968
            // The second regexp deals with audio/ urls.
6969
            $content = preg_replace(
6970
                "|(flashvars=\"file=)([^/]+)/|",
6971
                "$1".api_get_path(
6972
                    REL_COURSE_PATH
6973
                ).$courseInfo['path'].'/document/$2/',
6974
                $content
6975
            );
6976
            // For flv player: To prevent edition problem with firefox,
6977
            // we have to use a strange tip (don't blame me please).
6978
            $content = str_replace(
6979
                '</body>',
6980
                '<style type="text/css">body{}</style></body>',
6981
                $content
6982
            );
6983
        }
6984
6985
        if (!file_exists($filepath.$filename)) {
6986
            if ($fp = @fopen($filepath.$filename, 'w')) {
6987
                fputs($fp, $content);
6988
                fclose($fp);
6989
6990
                $file_size = filesize($filepath.$filename);
6991
                $save_file_path = $dir.$filename;
6992
6993
                $document = DocumentManager::addDocument(
6994
                    $courseInfo,
6995
                    $save_file_path,
6996
                    'file',
6997
                    $file_size,
6998
                    $tmp_filename,
6999
                    '',
7000
                    0, //readonly
7001
                    true,
7002
                    null,
7003
                    $sessionId,
7004
                    $creatorId
7005
                );
7006
7007
                $document_id = $document->getId();
7008
7009
                if ($document_id) {
7010
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7011
                    $new_title = $originalTitle;
7012
7013
                    if ($new_comment || $new_title) {
7014
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7015
                        $ct = '';
7016
                        if ($new_comment) {
7017
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7018
                        }
7019
                        if ($new_title) {
7020
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7021
                        }
7022
7023
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7024
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7025
                        Database::query($sql);
7026
                    }
7027
                }
7028
7029
                return $document_id;
7030
            }
7031
        }
7032
    }
7033
7034
    /**
7035
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7036
     *
7037
     * @param array $_course array
7038
     */
7039
    public function edit_document($_course)
7040
    {
7041
        $course_id = api_get_course_int_id();
7042
        $urlAppend = api_get_configuration_value('url_append');
7043
        // Please, do not modify this dirname formatting.
7044
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7045
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7046
7047
        if (strstr($dir, '..')) {
7048
            $dir = '/';
7049
        }
7050
7051
        if (isset($dir[0]) && $dir[0] == '.') {
7052
            $dir = substr($dir, 1);
7053
        }
7054
7055
        if (isset($dir[0]) && $dir[0] != '/') {
7056
            $dir = '/'.$dir;
7057
        }
7058
7059
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7060
            $dir .= '/';
7061
        }
7062
7063
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7064
        if (!is_dir($filepath)) {
7065
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7066
        }
7067
7068
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7069
7070
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7071
            $document_id = (int) $_POST['path'];
7072
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7073
            if (empty($documentInfo)) {
7074
                // Try with iid
7075
                $table = Database::get_course_table(TABLE_DOCUMENT);
7076
                $sql = "SELECT id, path FROM $table 
7077
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7078
                $res_doc = Database::query($sql);
7079
                $row = Database::fetch_array($res_doc);
7080
                if ($row) {
7081
                    $document_id = $row['id'];
7082
                    $documentPath = $row['path'];
7083
                }
7084
            } else {
7085
                $documentPath = $documentInfo['path'];
7086
            }
7087
7088
            $content = stripslashes($_POST['content_lp']);
7089
            $file = $filepath.$documentPath;
7090
7091
            if (!file_exists($file)) {
7092
                return false;
7093
            }
7094
7095
            if ($fp = @fopen($file, 'w')) {
7096
                $content = str_replace(
7097
                    api_get_path(WEB_COURSE_PATH),
7098
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7099
                    $content
7100
                );
7101
                // Change the path of mp3 to absolute.
7102
                // The first regexp deals with :// urls.
7103
                $content = preg_replace(
7104
                    "|(flashvars=\"file=)([^:/]+)/|",
7105
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7106
                    $content
7107
                );
7108
                // The second regexp deals with audio/ urls.
7109
                $content = preg_replace(
7110
                    "|(flashvars=\"file=)([^:/]+)/|",
7111
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7112
                    $content
7113
                );
7114
                fputs($fp, $content);
7115
                fclose($fp);
7116
7117
                $sql = "UPDATE $table_doc SET
7118
                            title='".Database::escape_string($_POST['title'])."'
7119
                        WHERE c_id = $course_id AND id = ".$document_id;
7120
                Database::query($sql);
7121
            }
7122
        }
7123
    }
7124
7125
    /**
7126
     * Displays the selected item, with a panel for manipulating the item.
7127
     *
7128
     * @param int    $item_id
7129
     * @param string $msg
7130
     * @param bool   $show_actions
7131
     *
7132
     * @return string
7133
     */
7134
    public function display_item($item_id, $msg = null, $show_actions = true)
7135
    {
7136
        $course_id = api_get_course_int_id();
7137
        $return = '';
7138
        if (is_numeric($item_id)) {
7139
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7140
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7141
                    WHERE lp.iid = ".intval($item_id);
7142
            $result = Database::query($sql);
7143
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7144
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7145
7146
                // Prevents wrong parent selection for document, see Bug#1251.
7147
                if ($row['item_type'] != 'dir') {
7148
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7149
                }
7150
7151
                if ($show_actions) {
7152
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7153
                }
7154
                $return .= '<div style="padding:10px;">';
7155
7156
                if ($msg != '') {
7157
                    $return .= $msg;
7158
                }
7159
7160
                $return .= '<h3>'.$row['title'].'</h3>';
7161
7162
                switch ($row['item_type']) {
7163
                    case TOOL_THREAD:
7164
                        $link = $this->rl_get_resource_link_for_learnpath(
7165
                            $course_id,
7166
                            $row['lp_id'],
7167
                            $item_id,
7168
                            0
7169
                        );
7170
                        $return .= Display::url(
7171
                            get_lang('GoToThread'),
7172
                            $link,
7173
                            ['class' => 'btn btn-primary']
7174
                        );
7175
                        break;
7176
                    case TOOL_FORUM:
7177
                        $return .= Display::url(
7178
                            get_lang('GoToForum'),
7179
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7180
                            ['class' => 'btn btn-primary']
7181
                        );
7182
                        break;
7183
                    case TOOL_QUIZ:
7184
                        if (!empty($row['path'])) {
7185
                            $exercise = new Exercise();
7186
                            $exercise->read($row['path']);
7187
                            $return .= $exercise->description.'<br />';
7188
                            $return .= Display::url(
7189
                                get_lang('GoToExercise'),
7190
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7191
                                ['class' => 'btn btn-primary']
7192
                            );
7193
                        }
7194
                        break;
7195
                    case TOOL_LP_FINAL_ITEM:
7196
                        $return .= $this->getSavedFinalItem();
7197
                        break;
7198
                    case TOOL_DOCUMENT:
7199
                    case TOOL_READOUT_TEXT:
7200
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7201
                        $sql_doc = "SELECT path FROM $tbl_doc
7202
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7203
                        $result = Database::query($sql_doc);
7204
                        $path_file = Database::result($result, 0, 0);
7205
                        $path_parts = pathinfo($path_file);
7206
                        // TODO: Correct the following naive comparisons.
7207
                        if (in_array($path_parts['extension'], [
7208
                            'html',
7209
                            'txt',
7210
                            'png',
7211
                            'jpg',
7212
                            'JPG',
7213
                            'jpeg',
7214
                            'JPEG',
7215
                            'gif',
7216
                            'swf',
7217
                            'pdf',
7218
                            'htm',
7219
                        ])) {
7220
                            $return .= $this->display_document($row['path'], true, true);
7221
                        }
7222
                        break;
7223
                    case TOOL_HOTPOTATOES:
7224
                        $return .= $this->display_document($row['path'], false, true);
7225
                        break;
7226
                }
7227
                $return .= '</div>';
7228
            }
7229
        }
7230
7231
        return $return;
7232
    }
7233
7234
    /**
7235
     * Shows the needed forms for editing a specific item.
7236
     *
7237
     * @param int $item_id
7238
     *
7239
     * @throws Exception
7240
     * @throws HTML_QuickForm_Error
7241
     *
7242
     * @return string
7243
     */
7244
    public function display_edit_item($item_id)
7245
    {
7246
        $course_id = api_get_course_int_id();
7247
        $return = '';
7248
        $item_id = (int) $item_id;
7249
7250
        if (empty($item_id)) {
7251
            return '';
7252
        }
7253
7254
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7255
        $sql = "SELECT * FROM $tbl_lp_item
7256
                WHERE iid = ".$item_id;
7257
        $res = Database::query($sql);
7258
        $row = Database::fetch_array($res);
7259
        switch ($row['item_type']) {
7260
            case 'dir':
7261
            case 'asset':
7262
            case 'sco':
7263
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7264
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7265
                    $return .= $this->display_item_form(
7266
                        $row['item_type'],
7267
                        get_lang('EditCurrentChapter').' :',
7268
                        'edit',
7269
                        $item_id,
7270
                        $row
7271
                    );
7272
                } else {
7273
                    $return .= $this->display_item_form(
7274
                        $row['item_type'],
7275
                        get_lang('EditCurrentChapter').' :',
7276
                        'edit_item',
7277
                        $item_id,
7278
                        $row
7279
                    );
7280
                }
7281
                break;
7282
            case TOOL_DOCUMENT:
7283
            case TOOL_READOUT_TEXT:
7284
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7285
                $sql = "SELECT lp.*, doc.path as dir
7286
                        FROM $tbl_lp_item as lp
7287
                        LEFT JOIN $tbl_doc as doc
7288
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7289
                        WHERE
7290
                            doc.c_id = $course_id AND
7291
                            lp.iid = ".$item_id;
7292
                $res_step = Database::query($sql);
7293
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7294
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7295
7296
                if ($row['item_type'] === TOOL_DOCUMENT) {
7297
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7298
                }
7299
7300
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7301
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7302
                }
7303
                break;
7304
            case TOOL_LINK:
7305
                $linkId = (int) $row['path'];
7306
                if (!empty($linkId)) {
7307
                    $table = Database::get_course_table(TABLE_LINK);
7308
                    $sql = 'SELECT url FROM '.$table.'
7309
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7310
                    $res_link = Database::query($sql);
7311
                    $row_link = Database::fetch_array($res_link);
7312
                    if (empty($row_link)) {
7313
                        // Try with id
7314
                        $sql = 'SELECT url FROM '.$table.'
7315
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7316
                        $res_link = Database::query($sql);
7317
                        $row_link = Database::fetch_array($res_link);
7318
                    }
7319
7320
                    if (is_array($row_link)) {
7321
                        $row['url'] = $row_link['url'];
7322
                    }
7323
                }
7324
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7325
                $return .= $this->display_link_form('edit', $item_id, $row);
7326
                break;
7327
            case TOOL_LP_FINAL_ITEM:
7328
                Session::write('finalItem', true);
7329
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7330
                $sql = "SELECT lp.*, doc.path as dir
7331
                        FROM $tbl_lp_item as lp
7332
                        LEFT JOIN $tbl_doc as doc
7333
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7334
                        WHERE
7335
                            doc.c_id = $course_id AND
7336
                            lp.iid = ".$item_id;
7337
                $res_step = Database::query($sql);
7338
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7339
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7340
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7341
                break;
7342
            case TOOL_QUIZ:
7343
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7344
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7345
                break;
7346
            case TOOL_HOTPOTATOES:
7347
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7348
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7349
                break;
7350
            case TOOL_STUDENTPUBLICATION:
7351
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7352
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7353
                break;
7354
            case TOOL_FORUM:
7355
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7356
                $return .= $this->display_forum_form('edit', $item_id, $row);
7357
                break;
7358
            case TOOL_THREAD:
7359
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7360
                $return .= $this->display_thread_form('edit', $item_id, $row);
7361
                break;
7362
        }
7363
7364
        return $return;
7365
    }
7366
7367
    /**
7368
     * Function that displays a list with al the resources that
7369
     * could be added to the learning path.
7370
     *
7371
     * @throws Exception
7372
     * @throws HTML_QuickForm_Error
7373
     *
7374
     * @return bool
7375
     */
7376
    public function display_resources()
7377
    {
7378
        $course_code = api_get_course_id();
7379
7380
        // Get all the docs.
7381
        $documents = $this->get_documents(true);
7382
7383
        // Get all the exercises.
7384
        $exercises = $this->get_exercises();
7385
7386
        // Get all the links.
7387
        $links = $this->get_links();
7388
7389
        // Get all the student publications.
7390
        $works = $this->get_student_publications();
7391
7392
        // Get all the forums.
7393
        $forums = $this->get_forums(null, $course_code);
7394
7395
        // Get the final item form (see BT#11048) .
7396
        $finish = $this->getFinalItemForm();
7397
7398
        $headers = [
7399
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7400
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7401
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7402
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7403
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7404
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7405
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7406
        ];
7407
7408
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7409
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7410
7411
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7412
7413
        echo Display::tabs(
7414
            $headers,
7415
            [
7416
                $documents,
7417
                $exercises,
7418
                $links,
7419
                $works,
7420
                $forums,
7421
                $dir,
7422
                $finish,
7423
            ],
7424
            'resource_tab',
7425
            [],
7426
            [],
7427
            $selected
7428
        );
7429
7430
        return true;
7431
    }
7432
7433
    /**
7434
     * Returns the extension of a document.
7435
     *
7436
     * @param string $filename
7437
     *
7438
     * @return string Extension (part after the last dot)
7439
     */
7440
    public function get_extension($filename)
7441
    {
7442
        $explode = explode('.', $filename);
7443
7444
        return $explode[count($explode) - 1];
7445
    }
7446
7447
    /**
7448
     * Displays a document by id.
7449
     *
7450
     * @param int  $id
7451
     * @param bool $show_title
7452
     * @param bool $iframe
7453
     * @param bool $edit_link
7454
     *
7455
     * @return string
7456
     */
7457
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7458
    {
7459
        $_course = api_get_course_info();
7460
        $course_id = api_get_course_int_id();
7461
        $id = (int) $id;
7462
        $return = '';
7463
        $table = Database::get_course_table(TABLE_DOCUMENT);
7464
        $sql_doc = "SELECT * FROM $table
7465
                    WHERE c_id = $course_id AND iid = $id";
7466
        $res_doc = Database::query($sql_doc);
7467
        $row_doc = Database::fetch_array($res_doc);
7468
7469
        // TODO: Add a path filter.
7470
        if ($iframe) {
7471
            $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>';
7472
        } else {
7473
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7474
        }
7475
7476
        return $return;
7477
    }
7478
7479
    /**
7480
     * Return HTML form to add/edit a quiz.
7481
     *
7482
     * @param string $action     Action (add/edit)
7483
     * @param int    $id         Item ID if already exists
7484
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7485
     *
7486
     * @throws Exception
7487
     *
7488
     * @return string HTML form
7489
     */
7490
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7491
    {
7492
        $course_id = api_get_course_int_id();
7493
        $id = (int) $id;
7494
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7495
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7496
7497
        if ($id != 0 && is_array($extra_info)) {
7498
            $item_title = $extra_info['title'];
7499
            $item_description = $extra_info['description'];
7500
        } elseif (is_numeric($extra_info)) {
7501
            $sql = "SELECT title, description
7502
                    FROM $tbl_quiz
7503
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7504
7505
            $result = Database::query($sql);
7506
            $row = Database::fetch_array($result);
7507
            $item_title = $row['title'];
7508
            $item_description = $row['description'];
7509
        } else {
7510
            $item_title = '';
7511
            $item_description = '';
7512
        }
7513
        $item_title = Security::remove_XSS($item_title);
7514
        $item_description = Security::remove_XSS($item_description);
7515
7516
        $parent = 0;
7517
        if ($id != 0 && is_array($extra_info)) {
7518
            $parent = $extra_info['parent_item_id'];
7519
        }
7520
7521
        $sql = "SELECT * FROM $tbl_lp_item 
7522
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7523
7524
        $result = Database::query($sql);
7525
        $arrLP = [];
7526
        while ($row = Database::fetch_array($result)) {
7527
            $arrLP[] = [
7528
                'id' => $row['iid'],
7529
                'item_type' => $row['item_type'],
7530
                'title' => $row['title'],
7531
                'path' => $row['path'],
7532
                'description' => $row['description'],
7533
                'parent_item_id' => $row['parent_item_id'],
7534
                'previous_item_id' => $row['previous_item_id'],
7535
                'next_item_id' => $row['next_item_id'],
7536
                'display_order' => $row['display_order'],
7537
                'max_score' => $row['max_score'],
7538
                'min_score' => $row['min_score'],
7539
                'mastery_score' => $row['mastery_score'],
7540
                'prerequisite' => $row['prerequisite'],
7541
                'max_time_allowed' => $row['max_time_allowed'],
7542
            ];
7543
        }
7544
7545
        $this->tree_array($arrLP);
7546
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7547
        unset($this->arrMenu);
7548
7549
        $form = new FormValidator(
7550
            'quiz_form',
7551
            'POST',
7552
            $this->getCurrentBuildingModeURL()
7553
        );
7554
        $defaults = [];
7555
7556
        if ($action === 'add') {
7557
            $legend = get_lang('CreateTheExercise');
7558
        } elseif ($action === 'move') {
7559
            $legend = get_lang('MoveTheCurrentExercise');
7560
        } else {
7561
            $legend = get_lang('EditCurrentExecice');
7562
        }
7563
7564
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7565
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7566
        }
7567
7568
        $form->addHeader($legend);
7569
7570
        if ($action != 'move') {
7571
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7572
            $defaults['title'] = $item_title;
7573
        }
7574
7575
        // Select for Parent item, root or chapter
7576
        $selectParent = $form->addSelect(
7577
            'parent',
7578
            get_lang('Parent'),
7579
            [],
7580
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7581
        );
7582
        $selectParent->addOption($this->name, 0);
7583
7584
        $arrHide = [
7585
            $id,
7586
        ];
7587
        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...
7588
            if ($action != 'add') {
7589
                if (
7590
                    ($arrLP[$i]['item_type'] == 'dir') &&
7591
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7592
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7593
                ) {
7594
                    $selectParent->addOption(
7595
                        $arrLP[$i]['title'],
7596
                        $arrLP[$i]['id'],
7597
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7598
                    );
7599
7600
                    if ($parent == $arrLP[$i]['id']) {
7601
                        $selectParent->setSelected($arrLP[$i]['id']);
7602
                    }
7603
                } else {
7604
                    $arrHide[] = $arrLP[$i]['id'];
7605
                }
7606
            } else {
7607
                if ($arrLP[$i]['item_type'] == 'dir') {
7608
                    $selectParent->addOption(
7609
                        $arrLP[$i]['title'],
7610
                        $arrLP[$i]['id'],
7611
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7612
                    );
7613
7614
                    if ($parent == $arrLP[$i]['id']) {
7615
                        $selectParent->setSelected($arrLP[$i]['id']);
7616
                    }
7617
                }
7618
            }
7619
        }
7620
7621
        if (is_array($arrLP)) {
7622
            reset($arrLP);
7623
        }
7624
7625
        $selectPrevious = $form->addSelect(
7626
            'previous',
7627
            get_lang('Position'),
7628
            [],
7629
            ['id' => 'previous']
7630
        );
7631
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7632
7633
        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...
7634
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7635
                $arrLP[$i]['id'] != $id
7636
            ) {
7637
                $selectPrevious->addOption(
7638
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7639
                    $arrLP[$i]['id']
7640
                );
7641
7642
                if (is_array($extra_info)) {
7643
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7644
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7645
                    }
7646
                } elseif ($action == 'add') {
7647
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7648
                }
7649
            }
7650
        }
7651
7652
        if ($action != 'move') {
7653
            $arrHide = [];
7654
            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...
7655
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7656
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7657
                }
7658
            }
7659
        }
7660
7661
        if ($action == 'add') {
7662
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7663
        } else {
7664
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7665
        }
7666
7667
        if ($action == 'move') {
7668
            $form->addHidden('title', $item_title);
7669
            $form->addHidden('description', $item_description);
7670
        }
7671
7672
        if (is_numeric($extra_info)) {
7673
            $form->addHidden('path', $extra_info);
7674
        } elseif (is_array($extra_info)) {
7675
            $form->addHidden('path', $extra_info['path']);
7676
        }
7677
7678
        $form->addHidden('type', TOOL_QUIZ);
7679
        $form->addHidden('post_time', time());
7680
        $form->setDefaults($defaults);
7681
7682
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7683
    }
7684
7685
    /**
7686
     * Addition of Hotpotatoes tests.
7687
     *
7688
     * @param string $action
7689
     * @param int    $id         Internal ID of the item
7690
     * @param string $extra_info
7691
     *
7692
     * @return string HTML structure to display the hotpotatoes addition formular
7693
     */
7694
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7695
    {
7696
        $course_id = api_get_course_int_id();
7697
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7698
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7699
7700
        if ($id != 0 && is_array($extra_info)) {
7701
            $item_title = stripslashes($extra_info['title']);
7702
            $item_description = stripslashes($extra_info['description']);
7703
        } elseif (is_numeric($extra_info)) {
7704
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7705
7706
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7707
                    WHERE
7708
                        c_id = ".$course_id." AND
7709
                        path LIKE '".$uploadPath."/%/%htm%' AND
7710
                        iid = ".(int) $extra_info."
7711
                    ORDER BY iid ASC";
7712
7713
            $res_hot = Database::query($sql);
7714
            $row = Database::fetch_array($res_hot);
7715
7716
            $item_title = $row['title'];
7717
            $item_description = $row['description'];
7718
7719
            if (!empty($row['comment'])) {
7720
                $item_title = $row['comment'];
7721
            }
7722
        } else {
7723
            $item_title = '';
7724
            $item_description = '';
7725
        }
7726
7727
        if ($id != 0 && is_array($extra_info)) {
7728
            $parent = $extra_info['parent_item_id'];
7729
        } else {
7730
            $parent = 0;
7731
        }
7732
7733
        $sql = "SELECT * FROM $tbl_lp_item
7734
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7735
        $result = Database::query($sql);
7736
        $arrLP = [];
7737
        while ($row = Database::fetch_array($result)) {
7738
            $arrLP[] = [
7739
                'id' => $row['id'],
7740
                'item_type' => $row['item_type'],
7741
                'title' => $row['title'],
7742
                'path' => $row['path'],
7743
                'description' => $row['description'],
7744
                'parent_item_id' => $row['parent_item_id'],
7745
                'previous_item_id' => $row['previous_item_id'],
7746
                'next_item_id' => $row['next_item_id'],
7747
                'display_order' => $row['display_order'],
7748
                'max_score' => $row['max_score'],
7749
                'min_score' => $row['min_score'],
7750
                'mastery_score' => $row['mastery_score'],
7751
                'prerequisite' => $row['prerequisite'],
7752
                'max_time_allowed' => $row['max_time_allowed'],
7753
            ];
7754
        }
7755
7756
        $legend = '<legend>';
7757
        if ($action == 'add') {
7758
            $legend .= get_lang('CreateTheExercise');
7759
        } elseif ($action == 'move') {
7760
            $legend .= get_lang('MoveTheCurrentExercise');
7761
        } else {
7762
            $legend .= get_lang('EditCurrentExecice');
7763
        }
7764
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7765
            $legend .= Display:: return_message(
7766
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7767
            );
7768
        }
7769
        $legend .= '</legend>';
7770
7771
        $return = '<form method="POST">';
7772
        $return .= $legend;
7773
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7774
        $return .= '<tr>';
7775
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7776
        $return .= '<td class="input">';
7777
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7778
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7779
        $arrHide = [
7780
            $id,
7781
        ];
7782
7783
        if (count($arrLP) > 0) {
7784
            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...
7785
                if ($action != 'add') {
7786
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7787
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7788
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7789
                    ) {
7790
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7791
                    } else {
7792
                        $arrHide[] = $arrLP[$i]['id'];
7793
                    }
7794
                } else {
7795
                    if ($arrLP[$i]['item_type'] == 'dir') {
7796
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7797
                    }
7798
                }
7799
            }
7800
            reset($arrLP);
7801
        }
7802
7803
        $return .= '</select>';
7804
        $return .= '</td>';
7805
        $return .= '</tr>';
7806
        $return .= '<tr>';
7807
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7808
        $return .= '<td class="input">';
7809
        $return .= '<select id="previous" name="previous" size="1">';
7810
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7811
7812
        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...
7813
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7814
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7815
                    $selected = 'selected="selected" ';
7816
                } elseif ($action == 'add') {
7817
                    $selected = 'selected="selected" ';
7818
                } else {
7819
                    $selected = '';
7820
                }
7821
7822
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7823
            }
7824
        }
7825
7826
        $return .= '</select>';
7827
        $return .= '</td>';
7828
        $return .= '</tr>';
7829
7830
        if ($action != 'move') {
7831
            $return .= '<tr>';
7832
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7833
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7834
            $return .= '</tr>';
7835
            $id_prerequisite = 0;
7836
            if (is_array($arrLP) && count($arrLP) > 0) {
7837
                foreach ($arrLP as $key => $value) {
7838
                    if ($value['id'] == $id) {
7839
                        $id_prerequisite = $value['prerequisite'];
7840
                        break;
7841
                    }
7842
                }
7843
7844
                $arrHide = [];
7845
                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...
7846
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7847
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7848
                    }
7849
                }
7850
            }
7851
        }
7852
7853
        $return .= '<tr>';
7854
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7855
            get_lang('SaveHotpotatoes').'</button></td>';
7856
        $return .= '</tr>';
7857
        $return .= '</table>';
7858
7859
        if ($action == 'move') {
7860
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7861
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7862
        }
7863
7864
        if (is_numeric($extra_info)) {
7865
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7866
        } elseif (is_array($extra_info)) {
7867
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7868
        }
7869
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7870
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7871
        $return .= '</form>';
7872
7873
        return $return;
7874
    }
7875
7876
    /**
7877
     * Return the form to display the forum edit/add option.
7878
     *
7879
     * @param string $action
7880
     * @param int    $id         ID of the lp_item if already exists
7881
     * @param string $extra_info
7882
     *
7883
     * @throws Exception
7884
     *
7885
     * @return string HTML form
7886
     */
7887
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7888
    {
7889
        $course_id = api_get_course_int_id();
7890
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7891
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7892
7893
        if ($id != 0 && is_array($extra_info)) {
7894
            $item_title = stripslashes($extra_info['title']);
7895
        } elseif (is_numeric($extra_info)) {
7896
            $sql = "SELECT forum_title as title, forum_comment as comment
7897
                    FROM $tbl_forum
7898
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7899
7900
            $result = Database::query($sql);
7901
            $row = Database::fetch_array($result);
7902
7903
            $item_title = $row['title'];
7904
            $item_description = $row['comment'];
7905
        } else {
7906
            $item_title = '';
7907
            $item_description = '';
7908
        }
7909
7910
        if ($id != 0 && is_array($extra_info)) {
7911
            $parent = $extra_info['parent_item_id'];
7912
        } else {
7913
            $parent = 0;
7914
        }
7915
7916
        $sql = "SELECT * FROM $tbl_lp_item
7917
                WHERE
7918
                    c_id = $course_id AND
7919
                    lp_id = ".$this->lp_id;
7920
        $result = Database::query($sql);
7921
        $arrLP = [];
7922
        while ($row = Database::fetch_array($result)) {
7923
            $arrLP[] = [
7924
                'id' => $row['iid'],
7925
                'item_type' => $row['item_type'],
7926
                'title' => $row['title'],
7927
                'path' => $row['path'],
7928
                'description' => $row['description'],
7929
                'parent_item_id' => $row['parent_item_id'],
7930
                'previous_item_id' => $row['previous_item_id'],
7931
                'next_item_id' => $row['next_item_id'],
7932
                'display_order' => $row['display_order'],
7933
                'max_score' => $row['max_score'],
7934
                'min_score' => $row['min_score'],
7935
                'mastery_score' => $row['mastery_score'],
7936
                'prerequisite' => $row['prerequisite'],
7937
            ];
7938
        }
7939
7940
        $this->tree_array($arrLP);
7941
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7942
        unset($this->arrMenu);
7943
7944
        if ($action == 'add') {
7945
            $legend = get_lang('CreateTheForum');
7946
        } elseif ($action == 'move') {
7947
            $legend = get_lang('MoveTheCurrentForum');
7948
        } else {
7949
            $legend = get_lang('EditCurrentForum');
7950
        }
7951
7952
        $form = new FormValidator(
7953
            'forum_form',
7954
            'POST',
7955
            $this->getCurrentBuildingModeURL()
7956
        );
7957
        $defaults = [];
7958
7959
        $form->addHeader($legend);
7960
7961
        if ($action != 'move') {
7962
            $form->addText(
7963
                'title',
7964
                get_lang('Title'),
7965
                true,
7966
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
7967
            );
7968
            $defaults['title'] = $item_title;
7969
        }
7970
7971
        $selectParent = $form->addSelect(
7972
            'parent',
7973
            get_lang('Parent'),
7974
            [],
7975
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7976
        );
7977
        $selectParent->addOption($this->name, 0);
7978
        $arrHide = [
7979
            $id,
7980
        ];
7981
        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...
7982
            if ($action != 'add') {
7983
                if ($arrLP[$i]['item_type'] == 'dir' &&
7984
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7985
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7986
                ) {
7987
                    $selectParent->addOption(
7988
                        $arrLP[$i]['title'],
7989
                        $arrLP[$i]['id'],
7990
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7991
                    );
7992
7993
                    if ($parent == $arrLP[$i]['id']) {
7994
                        $selectParent->setSelected($arrLP[$i]['id']);
7995
                    }
7996
                } else {
7997
                    $arrHide[] = $arrLP[$i]['id'];
7998
                }
7999
            } else {
8000
                if ($arrLP[$i]['item_type'] == 'dir') {
8001
                    $selectParent->addOption(
8002
                        $arrLP[$i]['title'],
8003
                        $arrLP[$i]['id'],
8004
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8005
                    );
8006
8007
                    if ($parent == $arrLP[$i]['id']) {
8008
                        $selectParent->setSelected($arrLP[$i]['id']);
8009
                    }
8010
                }
8011
            }
8012
        }
8013
8014
        if (is_array($arrLP)) {
8015
            reset($arrLP);
8016
        }
8017
8018
        $selectPrevious = $form->addSelect(
8019
            'previous',
8020
            get_lang('Position'),
8021
            [],
8022
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8023
        );
8024
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8025
8026
        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...
8027
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8028
                $arrLP[$i]['id'] != $id
8029
            ) {
8030
                $selectPrevious->addOption(
8031
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8032
                    $arrLP[$i]['id']
8033
                );
8034
8035
                if (isset($extra_info['previous_item_id']) &&
8036
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8037
                ) {
8038
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8039
                } elseif ($action == 'add') {
8040
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8041
                }
8042
            }
8043
        }
8044
8045
        if ($action != 'move') {
8046
            $id_prerequisite = 0;
8047
            if (is_array($arrLP)) {
8048
                foreach ($arrLP as $key => $value) {
8049
                    if ($value['id'] == $id) {
8050
                        $id_prerequisite = $value['prerequisite'];
8051
                        break;
8052
                    }
8053
                }
8054
            }
8055
8056
            $arrHide = [];
8057
            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...
8058
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8059
                    if (isset($extra_info['previous_item_id']) &&
8060
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8061
                    ) {
8062
                        $s_selected_position = $arrLP[$i]['id'];
8063
                    } elseif ($action == 'add') {
8064
                        $s_selected_position = 0;
8065
                    }
8066
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8067
                }
8068
            }
8069
        }
8070
8071
        if ($action == 'add') {
8072
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8073
        } else {
8074
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8075
        }
8076
8077
        if ($action == 'move') {
8078
            $form->addHidden('title', $item_title);
8079
            $form->addHidden('description', $item_description);
8080
        }
8081
8082
        if (is_numeric($extra_info)) {
8083
            $form->addHidden('path', $extra_info);
8084
        } elseif (is_array($extra_info)) {
8085
            $form->addHidden('path', $extra_info['path']);
8086
        }
8087
        $form->addHidden('type', TOOL_FORUM);
8088
        $form->addHidden('post_time', time());
8089
        $form->setDefaults($defaults);
8090
8091
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8092
    }
8093
8094
    /**
8095
     * Return HTML form to add/edit forum threads.
8096
     *
8097
     * @param string $action
8098
     * @param int    $id         Item ID if already exists in learning path
8099
     * @param string $extra_info
8100
     *
8101
     * @throws Exception
8102
     *
8103
     * @return string HTML form
8104
     */
8105
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8106
    {
8107
        $course_id = api_get_course_int_id();
8108
        if (empty($course_id)) {
8109
            return null;
8110
        }
8111
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8112
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8113
8114
        if ($id != 0 && is_array($extra_info)) {
8115
            $item_title = stripslashes($extra_info['title']);
8116
        } elseif (is_numeric($extra_info)) {
8117
            $sql = "SELECT thread_title as title FROM $tbl_forum
8118
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8119
8120
            $result = Database::query($sql);
8121
            $row = Database::fetch_array($result);
8122
8123
            $item_title = $row['title'];
8124
            $item_description = '';
8125
        } else {
8126
            $item_title = '';
8127
            $item_description = '';
8128
        }
8129
8130
        if ($id != 0 && is_array($extra_info)) {
8131
            $parent = $extra_info['parent_item_id'];
8132
        } else {
8133
            $parent = 0;
8134
        }
8135
8136
        $sql = "SELECT * FROM $tbl_lp_item
8137
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8138
        $result = Database::query($sql);
8139
8140
        $arrLP = [];
8141
        while ($row = Database::fetch_array($result)) {
8142
            $arrLP[] = [
8143
                'id' => $row['iid'],
8144
                'item_type' => $row['item_type'],
8145
                'title' => $row['title'],
8146
                'path' => $row['path'],
8147
                'description' => $row['description'],
8148
                'parent_item_id' => $row['parent_item_id'],
8149
                'previous_item_id' => $row['previous_item_id'],
8150
                'next_item_id' => $row['next_item_id'],
8151
                'display_order' => $row['display_order'],
8152
                'max_score' => $row['max_score'],
8153
                'min_score' => $row['min_score'],
8154
                'mastery_score' => $row['mastery_score'],
8155
                'prerequisite' => $row['prerequisite'],
8156
            ];
8157
        }
8158
8159
        $this->tree_array($arrLP);
8160
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8161
        unset($this->arrMenu);
8162
8163
        $form = new FormValidator(
8164
            'thread_form',
8165
            'POST',
8166
            $this->getCurrentBuildingModeURL()
8167
        );
8168
        $defaults = [];
8169
8170
        if ($action == 'add') {
8171
            $legend = get_lang('CreateTheForum');
8172
        } elseif ($action == 'move') {
8173
            $legend = get_lang('MoveTheCurrentForum');
8174
        } else {
8175
            $legend = get_lang('EditCurrentForum');
8176
        }
8177
8178
        $form->addHeader($legend);
8179
        $selectParent = $form->addSelect(
8180
            'parent',
8181
            get_lang('Parent'),
8182
            [],
8183
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8184
        );
8185
        $selectParent->addOption($this->name, 0);
8186
8187
        $arrHide = [
8188
            $id,
8189
        ];
8190
8191
        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...
8192
            if ($action != 'add') {
8193
                if (
8194
                    ($arrLP[$i]['item_type'] == 'dir') &&
8195
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8196
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8197
                ) {
8198
                    $selectParent->addOption(
8199
                        $arrLP[$i]['title'],
8200
                        $arrLP[$i]['id'],
8201
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8202
                    );
8203
8204
                    if ($parent == $arrLP[$i]['id']) {
8205
                        $selectParent->setSelected($arrLP[$i]['id']);
8206
                    }
8207
                } else {
8208
                    $arrHide[] = $arrLP[$i]['id'];
8209
                }
8210
            } else {
8211
                if ($arrLP[$i]['item_type'] == 'dir') {
8212
                    $selectParent->addOption(
8213
                        $arrLP[$i]['title'],
8214
                        $arrLP[$i]['id'],
8215
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8216
                    );
8217
8218
                    if ($parent == $arrLP[$i]['id']) {
8219
                        $selectParent->setSelected($arrLP[$i]['id']);
8220
                    }
8221
                }
8222
            }
8223
        }
8224
8225
        if ($arrLP != null) {
8226
            reset($arrLP);
8227
        }
8228
8229
        $selectPrevious = $form->addSelect(
8230
            'previous',
8231
            get_lang('Position'),
8232
            [],
8233
            ['id' => 'previous']
8234
        );
8235
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8236
8237
        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...
8238
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8239
                $selectPrevious->addOption(
8240
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8241
                    $arrLP[$i]['id']
8242
                );
8243
8244
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8245
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8246
                } elseif ($action == 'add') {
8247
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8248
                }
8249
            }
8250
        }
8251
8252
        if ($action != 'move') {
8253
            $form->addText(
8254
                'title',
8255
                get_lang('Title'),
8256
                true,
8257
                ['id' => 'idTitle']
8258
            );
8259
            $defaults['title'] = $item_title;
8260
8261
            $id_prerequisite = 0;
8262
            if ($arrLP != null) {
8263
                foreach ($arrLP as $key => $value) {
8264
                    if ($value['id'] == $id) {
8265
                        $id_prerequisite = $value['prerequisite'];
8266
                        break;
8267
                    }
8268
                }
8269
            }
8270
8271
            $arrHide = [];
8272
            $s_selected_position = 0;
8273
            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...
8274
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8275
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8276
                        $s_selected_position = $arrLP[$i]['id'];
8277
                    } elseif ($action == 'add') {
8278
                        $s_selected_position = 0;
8279
                    }
8280
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8281
                }
8282
            }
8283
8284
            $selectPrerequisites = $form->addSelect(
8285
                'prerequisites',
8286
                get_lang('LearnpathPrerequisites'),
8287
                [],
8288
                ['id' => 'prerequisites']
8289
            );
8290
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8291
8292
            foreach ($arrHide as $key => $value) {
8293
                $selectPrerequisites->addOption($value['value'], $key);
8294
8295
                if ($key == $s_selected_position && $action == 'add') {
8296
                    $selectPrerequisites->setSelected($key);
8297
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8298
                    $selectPrerequisites->setSelected($key);
8299
                }
8300
            }
8301
        }
8302
8303
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8304
8305
        if ($action == 'move') {
8306
            $form->addHidden('title', $item_title);
8307
            $form->addHidden('description', $item_description);
8308
        }
8309
8310
        if (is_numeric($extra_info)) {
8311
            $form->addHidden('path', $extra_info);
8312
        } elseif (is_array($extra_info)) {
8313
            $form->addHidden('path', $extra_info['path']);
8314
        }
8315
8316
        $form->addHidden('type', TOOL_THREAD);
8317
        $form->addHidden('post_time', time());
8318
        $form->setDefaults($defaults);
8319
8320
        return $form->returnForm();
8321
    }
8322
8323
    /**
8324
     * Return the HTML form to display an item (generally a dir item).
8325
     *
8326
     * @param string $item_type
8327
     * @param string $title
8328
     * @param string $action
8329
     * @param int    $id
8330
     * @param string $extra_info
8331
     *
8332
     * @throws Exception
8333
     * @throws HTML_QuickForm_Error
8334
     *
8335
     * @return string HTML form
8336
     */
8337
    public function display_item_form(
8338
        $item_type,
8339
        $title = '',
8340
        $action = 'add_item',
8341
        $id = 0,
8342
        $extra_info = 'new'
8343
    ) {
8344
        $_course = api_get_course_info();
8345
8346
        global $charset;
8347
8348
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8349
        $item_title = '';
8350
        $item_description = '';
8351
        $item_path_fck = '';
8352
8353
        if ($id != 0 && is_array($extra_info)) {
8354
            $item_title = $extra_info['title'];
8355
            $item_description = $extra_info['description'];
8356
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8357
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8358
        }
8359
        $parent = 0;
8360
        if ($id != 0 && is_array($extra_info)) {
8361
            $parent = $extra_info['parent_item_id'];
8362
        }
8363
8364
        $id = (int) $id;
8365
        $sql = "SELECT * FROM $tbl_lp_item
8366
                WHERE
8367
                    lp_id = ".$this->lp_id." AND
8368
                    iid != $id";
8369
8370
        if ($item_type == 'dir') {
8371
            $sql .= " AND parent_item_id = 0";
8372
        }
8373
8374
        $result = Database::query($sql);
8375
        $arrLP = [];
8376
        while ($row = Database::fetch_array($result)) {
8377
            $arrLP[] = [
8378
                'id' => $row['iid'],
8379
                'item_type' => $row['item_type'],
8380
                'title' => $row['title'],
8381
                'path' => $row['path'],
8382
                'description' => $row['description'],
8383
                'parent_item_id' => $row['parent_item_id'],
8384
                'previous_item_id' => $row['previous_item_id'],
8385
                'next_item_id' => $row['next_item_id'],
8386
                'max_score' => $row['max_score'],
8387
                'min_score' => $row['min_score'],
8388
                'mastery_score' => $row['mastery_score'],
8389
                'prerequisite' => $row['prerequisite'],
8390
                'display_order' => $row['display_order'],
8391
            ];
8392
        }
8393
8394
        $this->tree_array($arrLP);
8395
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8396
        unset($this->arrMenu);
8397
8398
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8399
8400
        $form = new FormValidator('form', 'POST', $url);
8401
        $defaults['title'] = api_html_entity_decode(
8402
            $item_title,
8403
            ENT_QUOTES,
8404
            $charset
8405
        );
8406
        $defaults['description'] = $item_description;
8407
8408
        $form->addHeader($title);
8409
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8410
        $arrHide[0]['padding'] = 20;
8411
        $charset = api_get_system_encoding();
8412
        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...
8413
            if ($action != 'add') {
8414
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8415
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8416
                ) {
8417
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8418
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8419
                    if ($parent == $arrLP[$i]['id']) {
8420
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8421
                    }
8422
                }
8423
            } else {
8424
                if ($arrLP[$i]['item_type'] == 'dir') {
8425
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8426
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8427
                    if ($parent == $arrLP[$i]['id']) {
8428
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8429
                    }
8430
                }
8431
            }
8432
        }
8433
8434
        if ($action != 'move') {
8435
            $form->addElement('text', 'title', get_lang('Title'));
8436
            $form->applyFilter('title', 'html_filter');
8437
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8438
        } else {
8439
            $form->addElement('hidden', 'title');
8440
        }
8441
8442
        $parentSelect = $form->addElement(
8443
            'select',
8444
            'parent',
8445
            get_lang('Parent'),
8446
            '',
8447
            [
8448
                'id' => 'idParent',
8449
                'onchange' => "javascript: load_cbo(this.value);",
8450
            ]
8451
        );
8452
8453
        foreach ($arrHide as $key => $value) {
8454
            $parentSelect->addOption(
8455
                $value['value'],
8456
                $key,
8457
                'style="padding-left:'.$value['padding'].'px;"'
8458
            );
8459
            $lastPosition = $key;
8460
        }
8461
8462
        if (!empty($s_selected_parent)) {
8463
            $parentSelect->setSelected($s_selected_parent);
8464
        }
8465
8466
        if (is_array($arrLP)) {
8467
            reset($arrLP);
8468
        }
8469
        $arrHide = [];
8470
        // POSITION
8471
        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...
8472
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8473
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8474
                //this is the same!
8475
                if (isset($extra_info['previous_item_id']) &&
8476
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8477
                ) {
8478
                    $s_selected_position = $arrLP[$i]['id'];
8479
                } elseif ($action == 'add') {
8480
                    $s_selected_position = $arrLP[$i]['id'];
8481
                }
8482
8483
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8484
            }
8485
        }
8486
8487
        $position = $form->addElement(
8488
            'select',
8489
            'previous',
8490
            get_lang('Position'),
8491
            '',
8492
            ['id' => 'previous']
8493
        );
8494
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8495
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8496
8497
        $lastPosition = null;
8498
        foreach ($arrHide as $key => $value) {
8499
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8500
            $lastPosition = $key;
8501
        }
8502
8503
        if (!empty($s_selected_position)) {
8504
            $position->setSelected($s_selected_position);
8505
        }
8506
8507
        // When new chapter add at the end
8508
        if ($action == 'add_item') {
8509
            $position->setSelected($lastPosition);
8510
        }
8511
8512
        if (is_array($arrLP)) {
8513
            reset($arrLP);
8514
        }
8515
8516
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8517
8518
        //fix in order to use the tab
8519
        if ($item_type == 'dir') {
8520
            $form->addElement('hidden', 'type', 'dir');
8521
        }
8522
8523
        $extension = null;
8524
        if (!empty($item_path)) {
8525
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8526
        }
8527
8528
        //assets can't be modified
8529
        //$item_type == 'asset' ||
8530
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8531
            if ($item_type == 'sco') {
8532
                $form->addElement(
8533
                    'html',
8534
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8535
                );
8536
            }
8537
            $renderer = $form->defaultRenderer();
8538
            $renderer->setElementTemplate(
8539
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8540
                'content_lp'
8541
            );
8542
8543
            $relative_prefix = '';
8544
8545
            $editor_config = [
8546
                'ToolbarSet' => 'LearningPathDocuments',
8547
                'Width' => '100%',
8548
                'Height' => '500',
8549
                'FullPage' => true,
8550
                'CreateDocumentDir' => $relative_prefix,
8551
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8552
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8553
            ];
8554
8555
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8556
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8557
            $defaults['content_lp'] = file_get_contents($content_path);
8558
        }
8559
8560
        if (!empty($id)) {
8561
            $form->addHidden('id', $id);
8562
        }
8563
8564
        $form->addElement('hidden', 'type', $item_type);
8565
        $form->addElement('hidden', 'post_time', time());
8566
        $form->setDefaults($defaults);
8567
8568
        return $form->returnForm();
8569
    }
8570
8571
    /**
8572
     * @return string
8573
     */
8574
    public function getCurrentBuildingModeURL()
8575
    {
8576
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8577
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8578
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8579
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8580
8581
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8582
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8583
8584
        return $currentUrl;
8585
    }
8586
8587
    /**
8588
     * Returns the form to update or create a document.
8589
     *
8590
     * @param string $action     (add/edit)
8591
     * @param int    $id         ID of the lp_item (if already exists)
8592
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8593
     *
8594
     * @throws Exception
8595
     * @throws HTML_QuickForm_Error
8596
     *
8597
     * @return string HTML form
8598
     */
8599
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8600
    {
8601
        $course_id = api_get_course_int_id();
8602
        $_course = api_get_course_info();
8603
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8604
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8605
8606
        $no_display_edit_textarea = false;
8607
        $item_description = '';
8608
        //If action==edit document
8609
        //We don't display the document form if it's not an editable document (html or txt file)
8610
        if ($action === 'edit') {
8611
            if (is_array($extra_info)) {
8612
                $path_parts = pathinfo($extra_info['dir']);
8613
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8614
                    $no_display_edit_textarea = true;
8615
                }
8616
            }
8617
        }
8618
        $no_display_add = false;
8619
8620
        // If action==add an existing document
8621
        // We don't display the document form if it's not an editable document (html or txt file).
8622
        if ($action === 'add') {
8623
            if (is_numeric($extra_info)) {
8624
                $extra_info = (int) $extra_info;
8625
                $sql_doc = "SELECT path FROM $tbl_doc 
8626
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8627
                $result = Database::query($sql_doc);
8628
                $path_file = Database::result($result, 0, 0);
8629
                $path_parts = pathinfo($path_file);
8630
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8631
                    $no_display_add = true;
8632
                }
8633
            }
8634
        }
8635
        if ($id != 0 && is_array($extra_info)) {
8636
            $item_title = stripslashes($extra_info['title']);
8637
            $item_description = stripslashes($extra_info['description']);
8638
            if (empty($item_title)) {
8639
                $path_parts = pathinfo($extra_info['path']);
8640
                $item_title = stripslashes($path_parts['filename']);
8641
            }
8642
        } elseif (is_numeric($extra_info)) {
8643
            $sql = "SELECT path, title FROM $tbl_doc
8644
                    WHERE
8645
                        c_id = ".$course_id." AND
8646
                        iid = ".intval($extra_info);
8647
            $result = Database::query($sql);
8648
            $row = Database::fetch_array($result);
8649
            $item_title = $row['title'];
8650
            $item_title = str_replace('_', ' ', $item_title);
8651
            if (empty($item_title)) {
8652
                $path_parts = pathinfo($row['path']);
8653
                $item_title = stripslashes($path_parts['filename']);
8654
            }
8655
        } else {
8656
            $item_title = '';
8657
            $item_description = '';
8658
        }
8659
        $return = '<legend>';
8660
        $parent = 0;
8661
        if ($id != 0 && is_array($extra_info)) {
8662
            $parent = $extra_info['parent_item_id'];
8663
        }
8664
8665
        $sql = "SELECT * FROM $tbl_lp_item
8666
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8667
        $result = Database::query($sql);
8668
        $arrLP = [];
8669
8670
        while ($row = Database::fetch_array($result)) {
8671
            $arrLP[] = [
8672
                'id' => $row['iid'],
8673
                'item_type' => $row['item_type'],
8674
                'title' => $row['title'],
8675
                'path' => $row['path'],
8676
                'description' => $row['description'],
8677
                'parent_item_id' => $row['parent_item_id'],
8678
                'previous_item_id' => $row['previous_item_id'],
8679
                'next_item_id' => $row['next_item_id'],
8680
                'display_order' => $row['display_order'],
8681
                'max_score' => $row['max_score'],
8682
                'min_score' => $row['min_score'],
8683
                'mastery_score' => $row['mastery_score'],
8684
                'prerequisite' => $row['prerequisite'],
8685
            ];
8686
        }
8687
8688
        $this->tree_array($arrLP);
8689
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8690
        unset($this->arrMenu);
8691
8692
        if ($action == 'add') {
8693
            $return .= get_lang('CreateTheDocument');
8694
        } elseif ($action == 'move') {
8695
            $return .= get_lang('MoveTheCurrentDocument');
8696
        } else {
8697
            $return .= get_lang('EditTheCurrentDocument');
8698
        }
8699
        $return .= '</legend>';
8700
8701
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8702
            $return .= Display::return_message(
8703
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8704
                false
8705
            );
8706
        }
8707
        $form = new FormValidator(
8708
            'form',
8709
            'POST',
8710
            $this->getCurrentBuildingModeURL(),
8711
            '',
8712
            ['enctype' => 'multipart/form-data']
8713
        );
8714
        $defaults['title'] = Security::remove_XSS($item_title);
8715
        if (empty($item_title)) {
8716
            $defaults['title'] = Security::remove_XSS($item_title);
8717
        }
8718
        $defaults['description'] = $item_description;
8719
        $form->addElement('html', $return);
8720
8721
        if ($action != 'move') {
8722
            $data = $this->generate_lp_folder($_course);
8723
            if ($action != 'edit') {
8724
                $folders = DocumentManager::get_all_document_folders(
8725
                    $_course,
8726
                    0,
8727
                    true
8728
                );
8729
                DocumentManager::build_directory_selector(
8730
                    $folders,
8731
                    '',
8732
                    [],
8733
                    true,
8734
                    $form,
8735
                    'directory_parent_id'
8736
                );
8737
            }
8738
8739
            if (isset($data['id'])) {
8740
                $defaults['directory_parent_id'] = $data['id'];
8741
            }
8742
8743
            $form->addElement(
8744
                'text',
8745
                'title',
8746
                get_lang('Title'),
8747
                ['id' => 'idTitle', 'class' => 'col-md-4']
8748
            );
8749
            $form->applyFilter('title', 'html_filter');
8750
        }
8751
8752
        $arrHide[0]['value'] = $this->name;
8753
        $arrHide[0]['padding'] = 20;
8754
8755
        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...
8756
            if ($action != 'add') {
8757
                if ($arrLP[$i]['item_type'] == 'dir' &&
8758
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8759
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8760
                ) {
8761
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8762
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8763
                }
8764
            } else {
8765
                if ($arrLP[$i]['item_type'] == 'dir') {
8766
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8767
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8768
                }
8769
            }
8770
        }
8771
8772
        $parentSelect = $form->addSelect(
8773
            'parent',
8774
            get_lang('Parent'),
8775
            [],
8776
            [
8777
                'id' => 'idParent',
8778
                'onchange' => 'javascript: load_cbo(this.value);',
8779
            ]
8780
        );
8781
8782
        $my_count = 0;
8783
        foreach ($arrHide as $key => $value) {
8784
            if ($my_count != 0) {
8785
                // The LP name is also the first section and is not in the same charset like the other sections.
8786
                $value['value'] = Security::remove_XSS($value['value']);
8787
                $parentSelect->addOption(
8788
                    $value['value'],
8789
                    $key,
8790
                    'style="padding-left:'.$value['padding'].'px;"'
8791
                );
8792
            } else {
8793
                $value['value'] = Security::remove_XSS($value['value']);
8794
                $parentSelect->addOption(
8795
                    $value['value'],
8796
                    $key,
8797
                    'style="padding-left:'.$value['padding'].'px;"'
8798
                );
8799
            }
8800
            $my_count++;
8801
        }
8802
8803
        if (!empty($id)) {
8804
            $parentSelect->setSelected($parent);
8805
        } else {
8806
            $parent_item_id = Session::read('parent_item_id', 0);
8807
            $parentSelect->setSelected($parent_item_id);
8808
        }
8809
8810
        if (is_array($arrLP)) {
8811
            reset($arrLP);
8812
        }
8813
8814
        $arrHide = [];
8815
        // POSITION
8816
        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...
8817
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8818
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8819
            ) {
8820
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8821
            }
8822
        }
8823
8824
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8825
8826
        $position = $form->addSelect(
8827
            'previous',
8828
            get_lang('Position'),
8829
            [],
8830
            ['id' => 'previous']
8831
        );
8832
8833
        $position->addOption(get_lang('FirstPosition'), 0);
8834
        foreach ($arrHide as $key => $value) {
8835
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8836
            $position->addOption(
8837
                $value['value'],
8838
                $key,
8839
                'style="padding-left:'.$padding.'px;"'
8840
            );
8841
        }
8842
8843
        $position->setSelected($selectedPosition);
8844
8845
        if (is_array($arrLP)) {
8846
            reset($arrLP);
8847
        }
8848
8849
        if ($action != 'move') {
8850
            $arrHide = [];
8851
            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...
8852
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8853
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8854
                ) {
8855
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8856
                }
8857
            }
8858
8859
            if (!$no_display_add) {
8860
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8861
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8862
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8863
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8864
                ) {
8865
                    if (isset($_POST['content'])) {
8866
                        $content = stripslashes($_POST['content']);
8867
                    } elseif (is_array($extra_info)) {
8868
                        //If it's an html document or a text file
8869
                        if (!$no_display_edit_textarea) {
8870
                            $content = $this->display_document(
8871
                                $extra_info['path'],
8872
                                false,
8873
                                false
8874
                            );
8875
                        }
8876
                    } elseif (is_numeric($extra_info)) {
8877
                        $content = $this->display_document(
8878
                            $extra_info,
8879
                            false,
8880
                            false
8881
                        );
8882
                    } else {
8883
                        $content = '';
8884
                    }
8885
8886
                    if (!$no_display_edit_textarea) {
8887
                        // We need to calculate here some specific settings for the online editor.
8888
                        // The calculated settings work for documents in the Documents tool
8889
                        // (on the root or in subfolders).
8890
                        // For documents in native scorm packages it is unclear whether the
8891
                        // online editor should be activated or not.
8892
8893
                        // A new document, it is in the root of the repository.
8894
                        $relative_path = '';
8895
                        $relative_prefix = '';
8896
                        if (is_array($extra_info) && $extra_info != 'new') {
8897
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8898
                            $relative_path = explode('/', $extra_info['dir']);
8899
                            $cnt = count($relative_path) - 2;
8900
                            if ($cnt < 0) {
8901
                                $cnt = 0;
8902
                            }
8903
                            $relative_prefix = str_repeat('../', $cnt);
8904
                            $relative_path = array_slice($relative_path, 1, $cnt);
8905
                            $relative_path = implode('/', $relative_path);
8906
                            if (strlen($relative_path) > 0) {
8907
                                $relative_path = $relative_path.'/';
8908
                            }
8909
                        } else {
8910
                            $result = $this->generate_lp_folder($_course);
8911
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8912
                            $relative_prefix = '../../';
8913
                        }
8914
8915
                        $editor_config = [
8916
                            'ToolbarSet' => 'LearningPathDocuments',
8917
                            'Width' => '100%',
8918
                            'Height' => '500',
8919
                            'FullPage' => true,
8920
                            'CreateDocumentDir' => $relative_prefix,
8921
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8922
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8923
                        ];
8924
8925
                        if ($_GET['action'] == 'add_item') {
8926
                            $class = 'add';
8927
                            $text = get_lang('LPCreateDocument');
8928
                        } else {
8929
                            if ($_GET['action'] == 'edit_item') {
8930
                                $class = 'save';
8931
                                $text = get_lang('SaveDocument');
8932
                            }
8933
                        }
8934
8935
                        $form->addButtonSave($text, 'submit_button');
8936
                        $renderer = $form->defaultRenderer();
8937
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8938
                        $form->addElement('html', '<div class="editor-lp">');
8939
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8940
                        $form->addElement('html', '</div>');
8941
                        $defaults['content_lp'] = $content;
8942
                    }
8943
                } elseif (is_numeric($extra_info)) {
8944
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8945
8946
                    $return = $this->display_document($extra_info, true, true, true);
8947
                    $form->addElement('html', $return);
8948
                }
8949
            }
8950
        }
8951
        if (isset($extra_info['item_type']) &&
8952
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8953
        ) {
8954
            $parentSelect->freeze();
8955
            $position->freeze();
8956
        }
8957
8958
        if ($action == 'move') {
8959
            $form->addElement('hidden', 'title', $item_title);
8960
            $form->addElement('hidden', 'description', $item_description);
8961
        }
8962
        if (is_numeric($extra_info)) {
8963
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8964
            $form->addElement('hidden', 'path', $extra_info);
8965
        } elseif (is_array($extra_info)) {
8966
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8967
            $form->addElement('hidden', 'path', $extra_info['path']);
8968
        }
8969
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8970
        $form->addElement('hidden', 'post_time', time());
8971
        $form->setDefaults($defaults);
8972
8973
        return $form->returnForm();
8974
    }
8975
8976
    /**
8977
     * Returns the form to update or create a read-out text.
8978
     *
8979
     * @param string $action     "add" or "edit"
8980
     * @param int    $id         ID of the lp_item (if already exists)
8981
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8982
     *
8983
     * @throws Exception
8984
     * @throws HTML_QuickForm_Error
8985
     *
8986
     * @return string HTML form
8987
     */
8988
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8989
    {
8990
        $course_id = api_get_course_int_id();
8991
        $_course = api_get_course_info();
8992
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8993
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8994
8995
        $no_display_edit_textarea = false;
8996
        $item_description = '';
8997
        //If action==edit document
8998
        //We don't display the document form if it's not an editable document (html or txt file)
8999
        if ($action == 'edit') {
9000
            if (is_array($extra_info)) {
9001
                $path_parts = pathinfo($extra_info['dir']);
9002
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9003
                    $no_display_edit_textarea = true;
9004
                }
9005
            }
9006
        }
9007
        $no_display_add = false;
9008
9009
        if ($id != 0 && is_array($extra_info)) {
9010
            $item_title = stripslashes($extra_info['title']);
9011
            $item_description = stripslashes($extra_info['description']);
9012
            $item_terms = stripslashes($extra_info['terms']);
9013
            if (empty($item_title)) {
9014
                $path_parts = pathinfo($extra_info['path']);
9015
                $item_title = stripslashes($path_parts['filename']);
9016
            }
9017
        } elseif (is_numeric($extra_info)) {
9018
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9019
            $result = Database::query($sql);
9020
            $row = Database::fetch_array($result);
9021
            $item_title = $row['title'];
9022
            $item_title = str_replace('_', ' ', $item_title);
9023
            if (empty($item_title)) {
9024
                $path_parts = pathinfo($row['path']);
9025
                $item_title = stripslashes($path_parts['filename']);
9026
            }
9027
        } else {
9028
            $item_title = '';
9029
            $item_description = '';
9030
        }
9031
9032
        if ($id != 0 && is_array($extra_info)) {
9033
            $parent = $extra_info['parent_item_id'];
9034
        } else {
9035
            $parent = 0;
9036
        }
9037
9038
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9039
        $result = Database::query($sql);
9040
        $arrLP = [];
9041
9042
        while ($row = Database::fetch_array($result)) {
9043
            $arrLP[] = [
9044
                'id' => $row['iid'],
9045
                'item_type' => $row['item_type'],
9046
                'title' => $row['title'],
9047
                'path' => $row['path'],
9048
                'description' => $row['description'],
9049
                'parent_item_id' => $row['parent_item_id'],
9050
                'previous_item_id' => $row['previous_item_id'],
9051
                'next_item_id' => $row['next_item_id'],
9052
                'display_order' => $row['display_order'],
9053
                'max_score' => $row['max_score'],
9054
                'min_score' => $row['min_score'],
9055
                'mastery_score' => $row['mastery_score'],
9056
                'prerequisite' => $row['prerequisite'],
9057
            ];
9058
        }
9059
9060
        $this->tree_array($arrLP);
9061
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9062
        unset($this->arrMenu);
9063
9064
        if ($action == 'add') {
9065
            $formHeader = get_lang('CreateTheDocument');
9066
        } else {
9067
            $formHeader = get_lang('EditTheCurrentDocument');
9068
        }
9069
9070
        if ('edit' === $action) {
9071
            $urlAudioIcon = Display::url(
9072
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9073
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9074
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9075
            );
9076
        } else {
9077
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9078
        }
9079
9080
        $form = new FormValidator(
9081
            'frm_add_reading',
9082
            'POST',
9083
            $this->getCurrentBuildingModeURL(),
9084
            '',
9085
            ['enctype' => 'multipart/form-data']
9086
        );
9087
        $form->addHeader($formHeader);
9088
        $form->addHtml(
9089
            Display::return_message(
9090
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9091
                'normal',
9092
                false
9093
            )
9094
        );
9095
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9096
        $defaults['description'] = $item_description;
9097
9098
        $data = $this->generate_lp_folder($_course);
9099
9100
        if ($action != 'edit') {
9101
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9102
            DocumentManager::build_directory_selector(
9103
                $folders,
9104
                '',
9105
                [],
9106
                true,
9107
                $form,
9108
                'directory_parent_id'
9109
            );
9110
        }
9111
9112
        if (isset($data['id'])) {
9113
            $defaults['directory_parent_id'] = $data['id'];
9114
        }
9115
9116
        $form->addElement(
9117
            'text',
9118
            'title',
9119
            get_lang('Title')
9120
        );
9121
        $form->applyFilter('title', 'trim');
9122
        $form->applyFilter('title', 'html_filter');
9123
9124
        $arrHide[0]['value'] = $this->name;
9125
        $arrHide[0]['padding'] = 20;
9126
9127
        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...
9128
            if ($action != 'add') {
9129
                if ($arrLP[$i]['item_type'] == 'dir' &&
9130
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9131
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9132
                ) {
9133
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9134
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9135
                }
9136
            } else {
9137
                if ($arrLP[$i]['item_type'] == 'dir') {
9138
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9139
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9140
                }
9141
            }
9142
        }
9143
9144
        $parent_select = $form->addSelect(
9145
            'parent',
9146
            get_lang('Parent'),
9147
            [],
9148
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9149
        );
9150
9151
        $my_count = 0;
9152
        foreach ($arrHide as $key => $value) {
9153
            if ($my_count != 0) {
9154
                // The LP name is also the first section and is not in the same charset like the other sections.
9155
                $value['value'] = Security::remove_XSS($value['value']);
9156
                $parent_select->addOption(
9157
                    $value['value'],
9158
                    $key,
9159
                    'style="padding-left:'.$value['padding'].'px;"'
9160
                );
9161
            } else {
9162
                $value['value'] = Security::remove_XSS($value['value']);
9163
                $parent_select->addOption(
9164
                    $value['value'],
9165
                    $key,
9166
                    'style="padding-left:'.$value['padding'].'px;"'
9167
                );
9168
            }
9169
            $my_count++;
9170
        }
9171
9172
        if (!empty($id)) {
9173
            $parent_select->setSelected($parent);
9174
        } else {
9175
            $parent_item_id = Session::read('parent_item_id', 0);
9176
            $parent_select->setSelected($parent_item_id);
9177
        }
9178
9179
        if (is_array($arrLP)) {
9180
            reset($arrLP);
9181
        }
9182
9183
        $arrHide = [];
9184
        $s_selected_position = null;
9185
9186
        // POSITION
9187
        $lastPosition = null;
9188
9189
        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...
9190
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9191
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9192
            ) {
9193
                if ((isset($extra_info['previous_item_id']) &&
9194
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9195
                ) {
9196
                    $s_selected_position = $arrLP[$i]['id'];
9197
                }
9198
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9199
            }
9200
            $lastPosition = $arrLP[$i]['id'];
9201
        }
9202
9203
        if (empty($s_selected_position)) {
9204
            $s_selected_position = $lastPosition;
9205
        }
9206
9207
        $position = $form->addSelect(
9208
            'previous',
9209
            get_lang('Position'),
9210
            []
9211
        );
9212
        $position->addOption(get_lang('FirstPosition'), 0);
9213
9214
        foreach ($arrHide as $key => $value) {
9215
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9216
            $position->addOption(
9217
                $value['value'],
9218
                $key,
9219
                'style="padding-left:'.$padding.'px;"'
9220
            );
9221
        }
9222
        $position->setSelected($s_selected_position);
9223
9224
        if (is_array($arrLP)) {
9225
            reset($arrLP);
9226
        }
9227
9228
        $arrHide = [];
9229
9230
        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...
9231
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9232
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9233
            ) {
9234
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9235
            }
9236
        }
9237
9238
        if (!$no_display_add) {
9239
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9240
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9241
9242
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9243
                if (!$no_display_edit_textarea) {
9244
                    $content = '';
9245
9246
                    if (isset($_POST['content'])) {
9247
                        $content = stripslashes($_POST['content']);
9248
                    } elseif (is_array($extra_info)) {
9249
                        $content = $this->display_document($extra_info['path'], false, false);
9250
                    } elseif (is_numeric($extra_info)) {
9251
                        $content = $this->display_document($extra_info, false, false);
9252
                    }
9253
9254
                    // A new document, it is in the root of the repository.
9255
                    if (is_array($extra_info) && $extra_info != 'new') {
9256
                    } else {
9257
                        $this->generate_lp_folder($_course);
9258
                    }
9259
9260
                    if ($_GET['action'] == 'add_item') {
9261
                        $text = get_lang('LPCreateDocument');
9262
                    } else {
9263
                        $text = get_lang('SaveDocument');
9264
                    }
9265
9266
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9267
                    $form
9268
                        ->defaultRenderer()
9269
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9270
                    $form->addButtonSave($text, 'submit_button');
9271
                    $defaults['content_lp'] = $content;
9272
                }
9273
            } elseif (is_numeric($extra_info)) {
9274
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9275
9276
                $return = $this->display_document($extra_info, true, true, true);
9277
                $form->addElement('html', $return);
9278
            }
9279
        }
9280
9281
        if (is_numeric($extra_info)) {
9282
            $form->addElement('hidden', 'path', $extra_info);
9283
        } elseif (is_array($extra_info)) {
9284
            $form->addElement('hidden', 'path', $extra_info['path']);
9285
        }
9286
9287
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9288
        $form->addElement('hidden', 'post_time', time());
9289
        $form->setDefaults($defaults);
9290
9291
        return $form->returnForm();
9292
    }
9293
9294
    /**
9295
     * @param array  $courseInfo
9296
     * @param string $content
9297
     * @param string $title
9298
     * @param int    $parentId
9299
     *
9300
     * @throws \Doctrine\ORM\ORMException
9301
     * @throws \Doctrine\ORM\OptimisticLockException
9302
     * @throws \Doctrine\ORM\TransactionRequiredException
9303
     *
9304
     * @return int
9305
     */
9306
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9307
    {
9308
        $creatorId = api_get_user_id();
9309
        $sessionId = api_get_session_id();
9310
9311
        // Generates folder
9312
        $result = $this->generate_lp_folder($courseInfo);
9313
        $dir = $result['dir'];
9314
9315
        if (empty($parentId) || $parentId == '/') {
9316
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9317
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9318
9319
            if ($parentId === '/') {
9320
                $dir = '/';
9321
            }
9322
9323
            // Please, do not modify this dirname formatting.
9324
            if (strstr($dir, '..')) {
9325
                $dir = '/';
9326
            }
9327
9328
            if (!empty($dir[0]) && $dir[0] == '.') {
9329
                $dir = substr($dir, 1);
9330
            }
9331
            if (!empty($dir[0]) && $dir[0] != '/') {
9332
                $dir = '/'.$dir;
9333
            }
9334
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9335
                $dir .= '/';
9336
            }
9337
        } else {
9338
            $parentInfo = DocumentManager::get_document_data_by_id(
9339
                $parentId,
9340
                $courseInfo['code']
9341
            );
9342
            if (!empty($parentInfo)) {
9343
                $dir = $parentInfo['path'].'/';
9344
            }
9345
        }
9346
9347
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9348
9349
        if (!is_dir($filepath)) {
9350
            $dir = '/';
9351
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9352
        }
9353
9354
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9355
9356
        if (!empty($title)) {
9357
            $title = api_replace_dangerous_char(stripslashes($title));
9358
        } else {
9359
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9360
        }
9361
9362
        $title = disable_dangerous_file($title);
9363
        $filename = $title;
9364
        $content = !empty($content) ? $content : $_POST['content_lp'];
9365
        $tmpFileName = $filename;
9366
9367
        $i = 0;
9368
        while (file_exists($filepath.$tmpFileName.'.html')) {
9369
            $tmpFileName = $filename.'_'.++$i;
9370
        }
9371
9372
        $filename = $tmpFileName.'.html';
9373
        $content = stripslashes($content);
9374
9375
        if (file_exists($filepath.$filename)) {
9376
            return 0;
9377
        }
9378
9379
        $putContent = file_put_contents($filepath.$filename, $content);
9380
9381
        if ($putContent === false) {
9382
            return 0;
9383
        }
9384
9385
        $fileSize = filesize($filepath.$filename);
9386
        $saveFilePath = $dir.$filename;
9387
9388
        $document = DocumentManager::addDocument(
9389
            $courseInfo,
9390
            $saveFilePath,
9391
            'file',
9392
            $fileSize,
9393
            $tmpFileName,
9394
            '',
9395
            0, //readonly
9396
            true,
9397
            null,
9398
            $sessionId,
9399
            $creatorId
9400
        );
9401
9402
        $documentId = $document->getId();
9403
9404
        if (!$document) {
9405
            return 0;
9406
        }
9407
9408
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9409
        $newTitle = $originalTitle;
9410
9411
        if ($newComment || $newTitle) {
9412
            $em = Database::getManager();
9413
9414
            if ($newComment) {
9415
                $document->setComment($newComment);
9416
            }
9417
9418
            if ($newTitle) {
9419
                $document->setTitle($newTitle);
9420
            }
9421
9422
            $em->persist($document);
9423
            $em->flush();
9424
        }
9425
9426
        return $documentId;
9427
    }
9428
9429
    /**
9430
     * Return HTML form to add/edit a link item.
9431
     *
9432
     * @param string $action     (add/edit)
9433
     * @param int    $id         Item ID if exists
9434
     * @param mixed  $extra_info
9435
     *
9436
     * @throws Exception
9437
     * @throws HTML_QuickForm_Error
9438
     *
9439
     * @return string HTML form
9440
     */
9441
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9442
    {
9443
        $course_id = api_get_course_int_id();
9444
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9445
        $tbl_link = Database::get_course_table(TABLE_LINK);
9446
9447
        $item_title = '';
9448
        $item_description = '';
9449
        $item_url = '';
9450
9451
        if ($id != 0 && is_array($extra_info)) {
9452
            $item_title = stripslashes($extra_info['title']);
9453
            $item_description = stripslashes($extra_info['description']);
9454
            $item_url = stripslashes($extra_info['url']);
9455
        } elseif (is_numeric($extra_info)) {
9456
            $extra_info = (int) $extra_info;
9457
            $sql = "SELECT title, description, url 
9458
                    FROM $tbl_link
9459
                    WHERE c_id = $course_id AND iid = $extra_info";
9460
            $result = Database::query($sql);
9461
            $row = Database::fetch_array($result);
9462
            $item_title = $row['title'];
9463
            $item_description = $row['description'];
9464
            $item_url = $row['url'];
9465
        }
9466
9467
        $form = new FormValidator(
9468
            'edit_link',
9469
            'POST',
9470
            $this->getCurrentBuildingModeURL()
9471
        );
9472
        $defaults = [];
9473
        $parent = 0;
9474
        if ($id != 0 && is_array($extra_info)) {
9475
            $parent = $extra_info['parent_item_id'];
9476
        }
9477
9478
        $sql = "SELECT * FROM $tbl_lp_item
9479
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9480
        $result = Database::query($sql);
9481
        $arrLP = [];
9482
9483
        while ($row = Database::fetch_array($result)) {
9484
            $arrLP[] = [
9485
                'id' => $row['id'],
9486
                'item_type' => $row['item_type'],
9487
                'title' => $row['title'],
9488
                'path' => $row['path'],
9489
                'description' => $row['description'],
9490
                'parent_item_id' => $row['parent_item_id'],
9491
                'previous_item_id' => $row['previous_item_id'],
9492
                'next_item_id' => $row['next_item_id'],
9493
                'display_order' => $row['display_order'],
9494
                'max_score' => $row['max_score'],
9495
                'min_score' => $row['min_score'],
9496
                'mastery_score' => $row['mastery_score'],
9497
                'prerequisite' => $row['prerequisite'],
9498
            ];
9499
        }
9500
9501
        $this->tree_array($arrLP);
9502
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9503
        unset($this->arrMenu);
9504
9505
        if ($action == 'add') {
9506
            $legend = get_lang('CreateTheLink');
9507
        } elseif ($action == 'move') {
9508
            $legend = get_lang('MoveCurrentLink');
9509
        } else {
9510
            $legend = get_lang('EditCurrentLink');
9511
        }
9512
9513
        $form->addHeader($legend);
9514
9515
        if ($action != 'move') {
9516
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9517
            $defaults['title'] = $item_title;
9518
        }
9519
9520
        $selectParent = $form->addSelect(
9521
            'parent',
9522
            get_lang('Parent'),
9523
            [],
9524
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9525
        );
9526
        $selectParent->addOption($this->name, 0);
9527
        $arrHide = [
9528
            $id,
9529
        ];
9530
9531
        $parent_item_id = Session::read('parent_item_id', 0);
9532
9533
        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...
9534
            if ($action != 'add') {
9535
                if (
9536
                    ($arrLP[$i]['item_type'] == 'dir') &&
9537
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9538
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9539
                ) {
9540
                    $selectParent->addOption(
9541
                        $arrLP[$i]['title'],
9542
                        $arrLP[$i]['id'],
9543
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9544
                    );
9545
9546
                    if ($parent == $arrLP[$i]['id']) {
9547
                        $selectParent->setSelected($arrLP[$i]['id']);
9548
                    }
9549
                } else {
9550
                    $arrHide[] = $arrLP[$i]['id'];
9551
                }
9552
            } else {
9553
                if ($arrLP[$i]['item_type'] == 'dir') {
9554
                    $selectParent->addOption(
9555
                        $arrLP[$i]['title'],
9556
                        $arrLP[$i]['id'],
9557
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9558
                    );
9559
9560
                    if ($parent_item_id == $arrLP[$i]['id']) {
9561
                        $selectParent->setSelected($arrLP[$i]['id']);
9562
                    }
9563
                }
9564
            }
9565
        }
9566
9567
        if (is_array($arrLP)) {
9568
            reset($arrLP);
9569
        }
9570
9571
        $selectPrevious = $form->addSelect(
9572
            'previous',
9573
            get_lang('Position'),
9574
            [],
9575
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9576
        );
9577
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9578
9579
        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...
9580
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9581
                $selectPrevious->addOption(
9582
                    $arrLP[$i]['title'],
9583
                    $arrLP[$i]['id']
9584
                );
9585
9586
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9587
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9588
                } elseif ($action == 'add') {
9589
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9590
                }
9591
            }
9592
        }
9593
9594
        if ($action != 'move') {
9595
            $urlAttributes = ['class' => 'learnpath_item_form'];
9596
9597
            if (is_numeric($extra_info)) {
9598
                $urlAttributes['disabled'] = 'disabled';
9599
            }
9600
9601
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9602
            $defaults['url'] = $item_url;
9603
            $arrHide = [];
9604
            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...
9605
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9606
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9607
                }
9608
            }
9609
        }
9610
9611
        if ($action == 'add') {
9612
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9613
        } else {
9614
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9615
        }
9616
9617
        if ($action == 'move') {
9618
            $form->addHidden('title', $item_title);
9619
            $form->addHidden('description', $item_description);
9620
        }
9621
9622
        if (is_numeric($extra_info)) {
9623
            $form->addHidden('path', $extra_info);
9624
        } elseif (is_array($extra_info)) {
9625
            $form->addHidden('path', $extra_info['path']);
9626
        }
9627
        $form->addHidden('type', TOOL_LINK);
9628
        $form->addHidden('post_time', time());
9629
        $form->setDefaults($defaults);
9630
9631
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9632
    }
9633
9634
    /**
9635
     * Return HTML form to add/edit a student publication (work).
9636
     *
9637
     * @param string $action
9638
     * @param int    $id         Item ID if already exists
9639
     * @param string $extra_info
9640
     *
9641
     * @throws Exception
9642
     *
9643
     * @return string HTML form
9644
     */
9645
    public function display_student_publication_form(
9646
        $action = 'add',
9647
        $id = 0,
9648
        $extra_info = ''
9649
    ) {
9650
        $course_id = api_get_course_int_id();
9651
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9652
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9653
9654
        $item_title = get_lang('Student_publication');
9655
        if ($id != 0 && is_array($extra_info)) {
9656
            $item_title = stripslashes($extra_info['title']);
9657
            $item_description = stripslashes($extra_info['description']);
9658
        } elseif (is_numeric($extra_info)) {
9659
            $extra_info = (int) $extra_info;
9660
            $sql = "SELECT title, description
9661
                    FROM $tbl_publication
9662
                    WHERE c_id = $course_id AND id = ".$extra_info;
9663
9664
            $result = Database::query($sql);
9665
            $row = Database::fetch_array($result);
9666
            if ($row) {
9667
                $item_title = $row['title'];
9668
            }
9669
        }
9670
9671
        $parent = 0;
9672
        if ($id != 0 && is_array($extra_info)) {
9673
            $parent = $extra_info['parent_item_id'];
9674
        }
9675
9676
        $sql = "SELECT * FROM $tbl_lp_item 
9677
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9678
        $result = Database::query($sql);
9679
        $arrLP = [];
9680
9681
        while ($row = Database::fetch_array($result)) {
9682
            $arrLP[] = [
9683
                'id' => $row['iid'],
9684
                'item_type' => $row['item_type'],
9685
                'title' => $row['title'],
9686
                'path' => $row['path'],
9687
                'description' => $row['description'],
9688
                'parent_item_id' => $row['parent_item_id'],
9689
                'previous_item_id' => $row['previous_item_id'],
9690
                'next_item_id' => $row['next_item_id'],
9691
                'display_order' => $row['display_order'],
9692
                'max_score' => $row['max_score'],
9693
                'min_score' => $row['min_score'],
9694
                'mastery_score' => $row['mastery_score'],
9695
                'prerequisite' => $row['prerequisite'],
9696
            ];
9697
        }
9698
9699
        $this->tree_array($arrLP);
9700
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9701
        unset($this->arrMenu);
9702
9703
        $form = new FormValidator('frm_student_publication', 'post', '#');
9704
9705
        if ($action == 'add') {
9706
            $form->addHeader(get_lang('Student_publication'));
9707
        } elseif ($action == 'move') {
9708
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9709
        } else {
9710
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9711
        }
9712
9713
        if ($action != 'move') {
9714
            $form->addText(
9715
                'title',
9716
                get_lang('Title'),
9717
                true,
9718
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9719
            );
9720
        }
9721
9722
        $parentSelect = $form->addSelect(
9723
            'parent',
9724
            get_lang('Parent'),
9725
            ['0' => $this->name],
9726
            [
9727
                'onchange' => 'javascript: load_cbo(this.value);',
9728
                'class' => 'learnpath_item_form',
9729
                'id' => 'idParent',
9730
            ]
9731
        );
9732
9733
        $arrHide = [$id];
9734
        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...
9735
            if ($action != 'add') {
9736
                if (
9737
                    ($arrLP[$i]['item_type'] == 'dir') &&
9738
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9739
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9740
                ) {
9741
                    $parentSelect->addOption(
9742
                        $arrLP[$i]['title'],
9743
                        $arrLP[$i]['id'],
9744
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9745
                    );
9746
9747
                    if ($parent == $arrLP[$i]['id']) {
9748
                        $parentSelect->setSelected($arrLP[$i]['id']);
9749
                    }
9750
                } else {
9751
                    $arrHide[] = $arrLP[$i]['id'];
9752
                }
9753
            } else {
9754
                if ($arrLP[$i]['item_type'] == 'dir') {
9755
                    $parentSelect->addOption(
9756
                        $arrLP[$i]['title'],
9757
                        $arrLP[$i]['id'],
9758
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9759
                    );
9760
9761
                    if ($parent == $arrLP[$i]['id']) {
9762
                        $parentSelect->setSelected($arrLP[$i]['id']);
9763
                    }
9764
                }
9765
            }
9766
        }
9767
9768
        if (is_array($arrLP)) {
9769
            reset($arrLP);
9770
        }
9771
9772
        $previousSelect = $form->addSelect(
9773
            'previous',
9774
            get_lang('Position'),
9775
            ['0' => get_lang('FirstPosition')],
9776
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9777
        );
9778
9779
        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...
9780
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9781
                $previousSelect->addOption(
9782
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9783
                    $arrLP[$i]['id']
9784
                );
9785
9786
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9787
                    $previousSelect->setSelected($arrLP[$i]['id']);
9788
                } elseif ($action == 'add') {
9789
                    $previousSelect->setSelected($arrLP[$i]['id']);
9790
                }
9791
            }
9792
        }
9793
9794
        if ($action == 'add') {
9795
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9796
        } else {
9797
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9798
        }
9799
9800
        if ($action == 'move') {
9801
            $form->addHidden('title', $item_title);
9802
            $form->addHidden('description', $item_description);
9803
        }
9804
9805
        if (is_numeric($extra_info)) {
9806
            $form->addHidden('path', $extra_info);
9807
        } elseif (is_array($extra_info)) {
9808
            $form->addHidden('path', $extra_info['path']);
9809
        }
9810
9811
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9812
        $form->addHidden('post_time', time());
9813
        $form->setDefaults(['title' => $item_title]);
9814
9815
        $return = '<div class="sectioncomment">';
9816
        $return .= $form->returnForm();
9817
        $return .= '</div>';
9818
9819
        return $return;
9820
    }
9821
9822
    /**
9823
     * Displays the menu for manipulating a step.
9824
     *
9825
     * @param id     $item_id
9826
     * @param string $item_type
9827
     *
9828
     * @return string
9829
     */
9830
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9831
    {
9832
        $_course = api_get_course_info();
9833
        $course_code = api_get_course_id();
9834
        $item_id = (int) $item_id;
9835
9836
        $return = '<div class="actions">';
9837
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9838
        $sql = "SELECT * FROM $tbl_lp_item 
9839
                WHERE iid = ".$item_id;
9840
        $result = Database::query($sql);
9841
        $row = Database::fetch_assoc($result);
9842
9843
        $audio_player = null;
9844
        // We display an audio player if needed.
9845
        if (!empty($row['audio'])) {
9846
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9847
9848
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9849
                .'<audio src="'.$webAudioPath.'" controls>'
9850
                .'</div><br>';
9851
        }
9852
9853
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9854
9855
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9856
            $return .= Display::url(
9857
                Display::return_icon(
9858
                    'edit.png',
9859
                    get_lang('Edit'),
9860
                    [],
9861
                    ICON_SIZE_SMALL
9862
                ),
9863
                $url.'&action=edit_item&path_item='.$row['path']
9864
            );
9865
9866
            $return .= Display::url(
9867
                Display::return_icon(
9868
                    'move.png',
9869
                    get_lang('Move'),
9870
                    [],
9871
                    ICON_SIZE_SMALL
9872
                ),
9873
                $url.'&action=move_item'
9874
            );
9875
        }
9876
9877
        // Commented for now as prerequisites cannot be added to chapters.
9878
        if ($item_type != 'dir') {
9879
            $return .= Display::url(
9880
                Display::return_icon(
9881
                    'accept.png',
9882
                    get_lang('LearnpathPrerequisites'),
9883
                    [],
9884
                    ICON_SIZE_SMALL
9885
                ),
9886
                $url.'&action=edit_item_prereq'
9887
            );
9888
        }
9889
        $return .= Display::url(
9890
            Display::return_icon(
9891
                'delete.png',
9892
                get_lang('Delete'),
9893
                [],
9894
                ICON_SIZE_SMALL
9895
            ),
9896
            $url.'&action=delete_item'
9897
        );
9898
9899
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9900
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9901
            if (empty($documentData)) {
9902
                // Try with iid
9903
                $table = Database::get_course_table(TABLE_DOCUMENT);
9904
                $sql = "SELECT path FROM $table
9905
                        WHERE 
9906
                              c_id = ".api_get_course_int_id()." AND 
9907
                              iid = ".$row['path']." AND 
9908
                              path NOT LIKE '%_DELETED_%'";
9909
                $result = Database::query($sql);
9910
                $documentData = Database::fetch_array($result);
9911
                if ($documentData) {
9912
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9913
                }
9914
            }
9915
            if (isset($documentData['absolute_path_from_document'])) {
9916
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9917
            }
9918
        }
9919
9920
        $return .= '</div>';
9921
9922
        if (!empty($audio_player)) {
9923
            $return .= $audio_player;
9924
        }
9925
9926
        return $return;
9927
    }
9928
9929
    /**
9930
     * Creates the javascript needed for filling up the checkboxes without page reload.
9931
     *
9932
     * @return string
9933
     */
9934
    public function get_js_dropdown_array()
9935
    {
9936
        $course_id = api_get_course_int_id();
9937
        $return = 'var child_name = new Array();'."\n";
9938
        $return .= 'var child_value = new Array();'."\n\n";
9939
        $return .= 'child_name[0] = new Array();'."\n";
9940
        $return .= 'child_value[0] = new Array();'."\n\n";
9941
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9942
        $sql = "SELECT * FROM ".$tbl_lp_item."
9943
                WHERE 
9944
                    c_id = $course_id AND 
9945
                    lp_id = ".$this->lp_id." AND 
9946
                    parent_item_id = 0
9947
                ORDER BY display_order ASC";
9948
        $res_zero = Database::query($sql);
9949
        $i = 0;
9950
9951
        while ($row_zero = Database::fetch_array($res_zero)) {
9952
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9953
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9954
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9955
                }
9956
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9957
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9958
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9959
            }
9960
        }
9961
        $return .= "\n";
9962
        $sql = "SELECT * FROM $tbl_lp_item
9963
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9964
        $res = Database::query($sql);
9965
        while ($row = Database::fetch_array($res)) {
9966
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9967
                           WHERE
9968
                                c_id = ".$course_id." AND
9969
                                parent_item_id = ".$row['iid']."
9970
                           ORDER BY display_order ASC";
9971
            $res_parent = Database::query($sql_parent);
9972
            $i = 0;
9973
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9974
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9975
9976
            while ($row_parent = Database::fetch_array($res_parent)) {
9977
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
9978
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9979
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9980
            }
9981
            $return .= "\n";
9982
        }
9983
9984
        $return .= "
9985
            function load_cbo(id) {
9986
                if (!id) {
9987
                    return false;
9988
                }
9989
            
9990
                var cbo = document.getElementById('previous');
9991
                for(var i = cbo.length - 1; i > 0; i--) {
9992
                    cbo.options[i] = null;
9993
                }
9994
            
9995
                var k=0;
9996
                for(var i = 1; i <= child_name[id].length; i++){
9997
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9998
                    option.style.paddingLeft = '40px';
9999
                    cbo.options[i] = option;
10000
                    k = i;
10001
                }
10002
            
10003
                cbo.options[k].selected = true;
10004
                $('#previous').selectpicker('refresh');
10005
            }";
10006
10007
        return $return;
10008
    }
10009
10010
    /**
10011
     * Display the form to allow moving an item.
10012
     *
10013
     * @param int $item_id Item ID
10014
     *
10015
     * @throws Exception
10016
     * @throws HTML_QuickForm_Error
10017
     *
10018
     * @return string HTML form
10019
     */
10020
    public function display_move_item($item_id)
10021
    {
10022
        $return = '';
10023
        if (is_numeric($item_id)) {
10024
            $item_id = (int) $item_id;
10025
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10026
10027
            $sql = "SELECT * FROM $tbl_lp_item
10028
                    WHERE iid = $item_id";
10029
            $res = Database::query($sql);
10030
            $row = Database::fetch_array($res);
10031
10032
            switch ($row['item_type']) {
10033
                case 'dir':
10034
                case 'asset':
10035
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10036
                    $return .= $this->display_item_form(
10037
                        $row['item_type'],
10038
                        get_lang('MoveCurrentChapter'),
10039
                        'move',
10040
                        $item_id,
10041
                        $row
10042
                    );
10043
                    break;
10044
                case TOOL_DOCUMENT:
10045
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10046
                    $return .= $this->display_document_form('move', $item_id, $row);
10047
                    break;
10048
                case TOOL_LINK:
10049
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10050
                    $return .= $this->display_link_form('move', $item_id, $row);
10051
                    break;
10052
                case TOOL_HOTPOTATOES:
10053
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10054
                    $return .= $this->display_link_form('move', $item_id, $row);
10055
                    break;
10056
                case TOOL_QUIZ:
10057
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10058
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10059
                    break;
10060
                case TOOL_STUDENTPUBLICATION:
10061
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10062
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10063
                    break;
10064
                case TOOL_FORUM:
10065
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10066
                    $return .= $this->display_forum_form('move', $item_id, $row);
10067
                    break;
10068
                case TOOL_THREAD:
10069
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10070
                    $return .= $this->display_forum_form('move', $item_id, $row);
10071
                    break;
10072
            }
10073
        }
10074
10075
        return $return;
10076
    }
10077
10078
    /**
10079
     * Return HTML form to allow prerequisites selection.
10080
     *
10081
     * @todo use FormValidator
10082
     *
10083
     * @param int Item ID
10084
     *
10085
     * @return string HTML form
10086
     */
10087
    public function display_item_prerequisites_form($item_id = 0)
10088
    {
10089
        $course_id = api_get_course_int_id();
10090
        $item_id = (int) $item_id;
10091
10092
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10093
10094
        /* Current prerequisite */
10095
        $sql = "SELECT * FROM $tbl_lp_item
10096
                WHERE iid = $item_id";
10097
        $result = Database::query($sql);
10098
        $row = Database::fetch_array($result);
10099
        $prerequisiteId = $row['prerequisite'];
10100
        $return = '<legend>';
10101
        $return .= get_lang('AddEditPrerequisites');
10102
        $return .= '</legend>';
10103
        $return .= '<form method="POST">';
10104
        $return .= '<div class="table-responsive">';
10105
        $return .= '<table class="table table-hover">';
10106
        $return .= '<thead>';
10107
        $return .= '<tr>';
10108
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10109
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10110
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10111
        $return .= '</tr>';
10112
        $return .= '</thead>';
10113
10114
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10115
        $return .= '<tbody>';
10116
        $return .= '<tr>';
10117
        $return .= '<td colspan="3">';
10118
        $return .= '<div class="radio learnpath"><label for="idNone">';
10119
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10120
        $return .= get_lang('None').'</label>';
10121
        $return .= '</div>';
10122
        $return .= '</tr>';
10123
10124
        $sql = "SELECT * FROM $tbl_lp_item
10125
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10126
        $result = Database::query($sql);
10127
        $arrLP = [];
10128
10129
        $selectedMinScore = [];
10130
        $selectedMaxScore = [];
10131
        $masteryScore = [];
10132
        while ($row = Database::fetch_array($result)) {
10133
            if ($row['iid'] == $item_id) {
10134
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10135
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10136
            }
10137
            $masteryScore[$row['iid']] = $row['mastery_score'];
10138
10139
            $arrLP[] = [
10140
                'id' => $row['iid'],
10141
                'item_type' => $row['item_type'],
10142
                'title' => $row['title'],
10143
                'ref' => $row['ref'],
10144
                'description' => $row['description'],
10145
                'parent_item_id' => $row['parent_item_id'],
10146
                'previous_item_id' => $row['previous_item_id'],
10147
                'next_item_id' => $row['next_item_id'],
10148
                'max_score' => $row['max_score'],
10149
                'min_score' => $row['min_score'],
10150
                'mastery_score' => $row['mastery_score'],
10151
                'prerequisite' => $row['prerequisite'],
10152
                'display_order' => $row['display_order'],
10153
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10154
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10155
            ];
10156
        }
10157
10158
        $this->tree_array($arrLP);
10159
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10160
        unset($this->arrMenu);
10161
10162
        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...
10163
            $item = $arrLP[$i];
10164
10165
            if ($item['id'] == $item_id) {
10166
                break;
10167
            }
10168
10169
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10170
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10171
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10172
10173
            $return .= '<tr>';
10174
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10175
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10176
            $return .= '<label for="id'.$item['id'].'">';
10177
            $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'].'" />';
10178
10179
            $icon_name = str_replace(' ', '', $item['item_type']);
10180
10181
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10182
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10183
            } else {
10184
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10185
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10186
                } else {
10187
                    $return .= Display::return_icon('folder_document.png');
10188
                }
10189
            }
10190
10191
            $return .= $item['title'].'</label>';
10192
            $return .= '</div>';
10193
            $return .= '</td>';
10194
10195
            if ($item['item_type'] == TOOL_QUIZ) {
10196
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10197
                $lpItemObj = new LpItem($course_id, $item['id']);
10198
                $exercise = new Exercise($course_id);
10199
                $exercise->read($lpItemObj->path);
10200
                $lpItemObj->max_score = $exercise->get_max_score();
10201
                $lpItemObj->update();
10202
                $item['max_score'] = $lpItemObj->max_score;
10203
10204
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10205
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10206
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10207
                }
10208
10209
                $return .= '<td>';
10210
                $return .= '<input 
10211
                    class="form-control" 
10212
                    size="4" maxlength="3" 
10213
                    name="min_'.$item['id'].'" 
10214
                    type="number" 
10215
                    min="0" 
10216
                    step="1" 
10217
                    max="'.$item['max_score'].'" 
10218
                    value="'.$selectedMinScoreValue.'" 
10219
                />';
10220
                $return .= '</td>';
10221
                $return .= '<td>';
10222
                $return .= '<input 
10223
                    class="form-control" 
10224
                    size="4" 
10225
                    maxlength="3" 
10226
                    name="max_'.$item['id'].'" 
10227
                    type="number" 
10228
                    min="0" 
10229
                    step="1" 
10230
                    max="'.$item['max_score'].'" 
10231
                    value="'.$selectedMaxScoreValue.'" 
10232
                />';
10233
                $return .= '</td>';
10234
            }
10235
10236
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10237
                $return .= '<td>';
10238
                $return .= '<input 
10239
                    size="4" 
10240
                    maxlength="3" 
10241
                    name="min_'.$item['id'].'" 
10242
                    type="number" 
10243
                    min="0" 
10244
                    step="1" 
10245
                    max="'.$item['max_score'].'" 
10246
                    value="'.$selectedMinScoreValue.'" 
10247
                />';
10248
                $return .= '</td>';
10249
                $return .= '<td>';
10250
                $return .= '<input 
10251
                    size="4" 
10252
                    maxlength="3" 
10253
                    name="max_'.$item['id'].'" 
10254
                    type="number" 
10255
                    min="0" 
10256
                    step="1" 
10257
                    max="'.$item['max_score'].'" 
10258
                    value="'.$selectedMaxScoreValue.'" 
10259
                />';
10260
                $return .= '</td>';
10261
            }
10262
            $return .= '</tr>';
10263
        }
10264
        $return .= '<tr>';
10265
        $return .= '</tr>';
10266
        $return .= '</tbody>';
10267
        $return .= '</table>';
10268
        $return .= '</div>';
10269
        $return .= '<div class="form-group">';
10270
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10271
            get_lang('ModifyPrerequisites').'</button>';
10272
        $return .= '</form>';
10273
10274
        return $return;
10275
    }
10276
10277
    /**
10278
     * Return HTML list to allow prerequisites selection for lp.
10279
     *
10280
     * @return string HTML form
10281
     */
10282
    public function display_lp_prerequisites_list()
10283
    {
10284
        $course_id = api_get_course_int_id();
10285
        $lp_id = $this->lp_id;
10286
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10287
10288
        // get current prerequisite
10289
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10290
        $result = Database::query($sql);
10291
        $row = Database::fetch_array($result);
10292
        $prerequisiteId = $row['prerequisite'];
10293
        $session_id = api_get_session_id();
10294
        $session_condition = api_get_session_condition($session_id, true, true);
10295
        $sql = "SELECT * FROM $tbl_lp
10296
                WHERE c_id = $course_id $session_condition
10297
                ORDER BY display_order ";
10298
        $rs = Database::query($sql);
10299
        $return = '';
10300
        $return .= '<select name="prerequisites" class="form-control">';
10301
        $return .= '<option value="0">'.get_lang('None').'</option>';
10302
        if (Database::num_rows($rs) > 0) {
10303
            while ($row = Database::fetch_array($rs)) {
10304
                if ($row['id'] == $lp_id) {
10305
                    continue;
10306
                }
10307
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10308
            }
10309
        }
10310
        $return .= '</select>';
10311
10312
        return $return;
10313
    }
10314
10315
    /**
10316
     * Creates a list with all the documents in it.
10317
     *
10318
     * @param bool $showInvisibleFiles
10319
     *
10320
     * @throws Exception
10321
     * @throws HTML_QuickForm_Error
10322
     *
10323
     * @return string
10324
     */
10325
    public function get_documents($showInvisibleFiles = false)
10326
    {
10327
        $course_info = api_get_course_info();
10328
        $sessionId = api_get_session_id();
10329
        $documentTree = DocumentManager::get_document_preview(
10330
            $course_info,
10331
            $this->lp_id,
10332
            null,
10333
            $sessionId,
10334
            true,
10335
            null,
10336
            null,
10337
            $showInvisibleFiles,
10338
            true
10339
        );
10340
10341
        $headers = [
10342
            get_lang('Files'),
10343
            get_lang('CreateTheDocument'),
10344
            get_lang('CreateReadOutText'),
10345
            get_lang('Upload'),
10346
        ];
10347
10348
        $form = new FormValidator(
10349
            'form_upload',
10350
            'POST',
10351
            $this->getCurrentBuildingModeURL(),
10352
            '',
10353
            ['enctype' => 'multipart/form-data']
10354
        );
10355
10356
        $folders = DocumentManager::get_all_document_folders(
10357
            api_get_course_info(),
10358
            0,
10359
            true
10360
        );
10361
10362
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10363
10364
        DocumentManager::build_directory_selector(
10365
            $folders,
10366
            $lpPathInfo['id'],
10367
            [],
10368
            true,
10369
            $form,
10370
            'directory_parent_id'
10371
        );
10372
10373
        $group = [
10374
            $form->createElement(
10375
                'radio',
10376
                'if_exists',
10377
                get_lang('UplWhatIfFileExists'),
10378
                get_lang('UplDoNothing'),
10379
                'nothing'
10380
            ),
10381
            $form->createElement(
10382
                'radio',
10383
                'if_exists',
10384
                null,
10385
                get_lang('UplOverwriteLong'),
10386
                'overwrite'
10387
            ),
10388
            $form->createElement(
10389
                'radio',
10390
                'if_exists',
10391
                null,
10392
                get_lang('UplRenameLong'),
10393
                'rename'
10394
            ),
10395
        ];
10396
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10397
10398
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10399
        $defaultFileExistsOption = 'rename';
10400
        if (!empty($fileExistsOption)) {
10401
            $defaultFileExistsOption = $fileExistsOption;
10402
        }
10403
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10404
10405
        // Check box options
10406
        $form->addElement(
10407
            'checkbox',
10408
            'unzip',
10409
            get_lang('Options'),
10410
            get_lang('Uncompress')
10411
        );
10412
10413
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10414
        $form->addMultipleUpload($url);
10415
        $new = $this->display_document_form('add', 0);
10416
        $frmReadOutText = $this->displayFrmReadOutText('add');
10417
        $tabs = Display::tabs(
10418
            $headers,
10419
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10420
            'subtab'
10421
        );
10422
10423
        return $tabs;
10424
    }
10425
10426
    /**
10427
     * Creates a list with all the exercises (quiz) in it.
10428
     *
10429
     * @return string
10430
     */
10431
    public function get_exercises()
10432
    {
10433
        $course_id = api_get_course_int_id();
10434
        $session_id = api_get_session_id();
10435
        $userInfo = api_get_user_info();
10436
10437
        // New for hotpotatoes.
10438
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10439
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10440
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10441
        $condition_session = api_get_session_condition($session_id, true, true);
10442
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10443
10444
        $activeCondition = ' active <> -1 ';
10445
        if ($setting) {
10446
            $activeCondition = ' active = 1 ';
10447
        }
10448
10449
        $categoryCondition = '';
10450
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10451
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10452
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10453
        }
10454
10455
        $keywordCondition = '';
10456
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10457
10458
        if (!empty($keyword)) {
10459
            $keyword = Database::escape_string($keyword);
10460
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10461
        }
10462
10463
        $sql_quiz = "SELECT * FROM $tbl_quiz
10464
                     WHERE 
10465
                            c_id = $course_id AND 
10466
                            $activeCondition
10467
                            $condition_session 
10468
                            $categoryCondition
10469
                            $keywordCondition
10470
                     ORDER BY title ASC";
10471
10472
        $sql_hot = "SELECT * FROM $tbl_doc
10473
                     WHERE 
10474
                        c_id = $course_id AND 
10475
                        path LIKE '".$uploadPath."/%/%htm%'  
10476
                        $condition_session
10477
                     ORDER BY id ASC";
10478
10479
        $res_quiz = Database::query($sql_quiz);
10480
        $res_hot = Database::query($sql_hot);
10481
10482
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10483
10484
        // Create a search-box
10485
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10486
        $form->addHidden('action', 'add_item');
10487
        $form->addHidden('type', 'step');
10488
        $form->addHidden('lp_id', $this->lp_id);
10489
        $form->addHidden('lp_build_selected', '2');
10490
10491
        $form->addCourseHiddenParams();
10492
        $form->addText(
10493
            'keyword',
10494
            get_lang('Search'),
10495
            false,
10496
            [
10497
                'aria-label' => get_lang('Search'),
10498
            ]
10499
        );
10500
10501
        if (api_get_configuration_value('allow_exercise_categories')) {
10502
            $manager = new ExerciseCategoryManager();
10503
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10504
            if (!empty($options)) {
10505
                $form->addSelect(
10506
                    'category_id',
10507
                    get_lang('Category'),
10508
                    $options,
10509
                    ['placeholder' => get_lang('SelectAnOption')]
10510
                );
10511
            }
10512
        }
10513
10514
        $form->addButtonSearch(get_lang('Search'));
10515
        $return = $form->returnForm();
10516
10517
        $return .= '<ul class="lp_resource">';
10518
10519
        $return .= '<li class="lp_resource_element">';
10520
        $return .= Display::return_icon('new_exercice.png');
10521
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10522
            get_lang('NewExercise').'</a>';
10523
        $return .= '</li>';
10524
10525
        $previewIcon = Display::return_icon(
10526
            'preview_view.png',
10527
            get_lang('Preview')
10528
        );
10529
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10530
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10531
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10532
10533
        // Display hotpotatoes
10534
        while ($row_hot = Database::fetch_array($res_hot)) {
10535
            $link = Display::url(
10536
                $previewIcon,
10537
                $exerciseUrl.'&file='.$row_hot['path'],
10538
                ['target' => '_blank']
10539
            );
10540
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10541
            $return .= '<a class="moved" href="#">';
10542
            $return .= Display::return_icon(
10543
                'move_everywhere.png',
10544
                get_lang('Move'),
10545
                [],
10546
                ICON_SIZE_TINY
10547
            );
10548
            $return .= '</a> ';
10549
            $return .= Display::return_icon('hotpotatoes_s.png');
10550
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10551
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10552
            $return .= '</li>';
10553
        }
10554
10555
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10556
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10557
            $title = strip_tags(
10558
                api_html_entity_decode($row_quiz['title'])
10559
            );
10560
10561
            $visibility = api_get_item_visibility(
10562
                ['real_id' => $course_id],
10563
                TOOL_QUIZ,
10564
                $row_quiz['iid'],
10565
                $session_id
10566
            );
10567
10568
            $link = Display::url(
10569
                $previewIcon,
10570
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10571
                ['target' => '_blank']
10572
            );
10573
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10574
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10575
            $return .= $quizIcon;
10576
            $sessionStar = api_get_session_image(
10577
                $row_quiz['session_id'],
10578
                $userInfo['status']
10579
            );
10580
            $return .= Display::url(
10581
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10582
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10583
                [
10584
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10585
                ]
10586
            );
10587
            $return .= '</li>';
10588
        }
10589
10590
        $return .= '</ul>';
10591
10592
        return $return;
10593
    }
10594
10595
    /**
10596
     * Creates a list with all the links in it.
10597
     *
10598
     * @return string
10599
     */
10600
    public function get_links()
10601
    {
10602
        $selfUrl = api_get_self();
10603
        $courseIdReq = api_get_cidreq();
10604
        $course = api_get_course_info();
10605
        $userInfo = api_get_user_info();
10606
10607
        $course_id = $course['real_id'];
10608
        $tbl_link = Database::get_course_table(TABLE_LINK);
10609
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10610
        $moveEverywhereIcon = Display::return_icon(
10611
            'move_everywhere.png',
10612
            get_lang('Move'),
10613
            [],
10614
            ICON_SIZE_TINY
10615
        );
10616
10617
        $session_id = api_get_session_id();
10618
        $condition_session = api_get_session_condition(
10619
            $session_id,
10620
            true,
10621
            true,
10622
            'link.session_id'
10623
        );
10624
10625
        $sql = "SELECT 
10626
                    link.id as link_id,
10627
                    link.title as link_title,
10628
                    link.session_id as link_session_id,
10629
                    link.category_id as category_id,
10630
                    link_category.category_title as category_title
10631
                FROM $tbl_link as link
10632
                LEFT JOIN $linkCategoryTable as link_category
10633
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10634
                WHERE link.c_id = $course_id $condition_session
10635
                ORDER BY link_category.category_title ASC, link.title ASC";
10636
        $result = Database::query($sql);
10637
        $categorizedLinks = [];
10638
        $categories = [];
10639
10640
        while ($link = Database::fetch_array($result)) {
10641
            if (!$link['category_id']) {
10642
                $link['category_title'] = get_lang('Uncategorized');
10643
            }
10644
            $categories[$link['category_id']] = $link['category_title'];
10645
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10646
        }
10647
10648
        $linksHtmlCode =
10649
            '<script>
10650
            function toggle_tool(tool, id) {
10651
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10652
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10653
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10654
                } else {
10655
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10656
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10657
                }
10658
            }
10659
        </script>
10660
10661
        <ul class="lp_resource">
10662
            <li class="lp_resource_element">
10663
                '.Display::return_icon('linksnew.gif').'
10664
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10665
                get_lang('LinkAdd').'
10666
                </a>
10667
            </li>';
10668
10669
        foreach ($categorizedLinks as $categoryId => $links) {
10670
            $linkNodes = null;
10671
            foreach ($links as $key => $linkInfo) {
10672
                $title = $linkInfo['link_title'];
10673
                $linkSessionId = $linkInfo['link_session_id'];
10674
10675
                $link = Display::url(
10676
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10677
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10678
                    ['target' => '_blank']
10679
                );
10680
10681
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10682
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10683
                    $linkNodes .=
10684
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10685
                        <a class="moved" href="#">'.
10686
                            $moveEverywhereIcon.
10687
                        '</a>
10688
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10689
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10690
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10691
                        Security::remove_XSS($title).$sessionStar.$link.
10692
                        '</a>
10693
                    </li>';
10694
                }
10695
            }
10696
            $linksHtmlCode .=
10697
                '<li>
10698
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10699
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10700
                    align="absbottom" />
10701
                </a>
10702
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10703
            </li>
10704
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10705
        }
10706
        $linksHtmlCode .= '</ul>';
10707
10708
        return $linksHtmlCode;
10709
    }
10710
10711
    /**
10712
     * Creates a list with all the student publications in it.
10713
     *
10714
     * @return string
10715
     */
10716
    public function get_student_publications()
10717
    {
10718
        $return = '<ul class="lp_resource">';
10719
        $return .= '<li class="lp_resource_element">';
10720
        $return .= Display::return_icon('works_new.gif');
10721
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10722
            get_lang('AddAssignmentPage').'</a>';
10723
        $return .= '</li>';
10724
10725
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10726
        $works = getWorkListTeacher(0, 100, null, null, null);
10727
        if (!empty($works)) {
10728
            foreach ($works as $work) {
10729
                $link = Display::url(
10730
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10731
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10732
                    ['target' => '_blank']
10733
                );
10734
10735
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10736
                $return .= '<a class="moved" href="#">';
10737
                $return .= Display::return_icon(
10738
                    'move_everywhere.png',
10739
                    get_lang('Move'),
10740
                    [],
10741
                    ICON_SIZE_TINY
10742
                );
10743
                $return .= '</a> ';
10744
10745
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10746
                $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.'">'.
10747
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10748
                </a>';
10749
10750
                $return .= '</li>';
10751
            }
10752
        }
10753
10754
        $return .= '</ul>';
10755
10756
        return $return;
10757
    }
10758
10759
    /**
10760
     * Creates a list with all the forums in it.
10761
     *
10762
     * @return string
10763
     */
10764
    public function get_forums()
10765
    {
10766
        require_once '../forum/forumfunction.inc.php';
10767
10768
        $forumCategories = get_forum_categories();
10769
        $forumsInNoCategory = get_forums_in_category(0);
10770
        if (!empty($forumsInNoCategory)) {
10771
            $forumCategories = array_merge(
10772
                $forumCategories,
10773
                [
10774
                    [
10775
                        'cat_id' => 0,
10776
                        'session_id' => 0,
10777
                        'visibility' => 1,
10778
                        'cat_comment' => null,
10779
                    ],
10780
                ]
10781
            );
10782
        }
10783
10784
        $forumList = get_forums();
10785
        $a_forums = [];
10786
        foreach ($forumCategories as $forumCategory) {
10787
            // The forums in this category.
10788
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10789
            if (!empty($forumsInCategory)) {
10790
                foreach ($forumList as $forum) {
10791
                    if (isset($forum['forum_category']) &&
10792
                        $forum['forum_category'] == $forumCategory['cat_id']
10793
                    ) {
10794
                        $a_forums[] = $forum;
10795
                    }
10796
                }
10797
            }
10798
        }
10799
10800
        $return = '<ul class="lp_resource">';
10801
10802
        // First add link
10803
        $return .= '<li class="lp_resource_element">';
10804
        $return .= Display::return_icon('new_forum.png');
10805
        $return .= Display::url(
10806
            get_lang('CreateANewForum'),
10807
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10808
                'action' => 'add',
10809
                'content' => 'forum',
10810
                'lp_id' => $this->lp_id,
10811
            ]),
10812
            ['title' => get_lang('CreateANewForum')]
10813
        );
10814
        $return .= '</li>';
10815
10816
        $return .= '<script>
10817
            function toggle_forum(forum_id) {
10818
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10819
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10820
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10821
                } else {
10822
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10823
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10824
                }
10825
            }
10826
        </script>';
10827
10828
        foreach ($a_forums as $forum) {
10829
            if (!empty($forum['forum_id'])) {
10830
                $link = Display::url(
10831
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10832
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10833
                    ['target' => '_blank']
10834
                );
10835
10836
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10837
                $return .= '<a class="moved" href="#">';
10838
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10839
                $return .= ' </a>';
10840
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10841
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10842
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10843
                            </a>
10844
                            <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">'.
10845
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10846
10847
                $return .= '</li>';
10848
10849
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10850
                $a_threads = get_threads($forum['forum_id']);
10851
                if (is_array($a_threads)) {
10852
                    foreach ($a_threads as $thread) {
10853
                        $link = Display::url(
10854
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10855
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10856
                            ['target' => '_blank']
10857
                        );
10858
10859
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10860
                        $return .= '&nbsp;<a class="moved" href="#">';
10861
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10862
                        $return .= ' </a>';
10863
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10864
                        $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.'">'.
10865
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10866
                        $return .= '</li>';
10867
                    }
10868
                }
10869
                $return .= '</div>';
10870
            }
10871
        }
10872
        $return .= '</ul>';
10873
10874
        return $return;
10875
    }
10876
10877
    /**
10878
     * // TODO: The output encoding should be equal to the system encoding.
10879
     *
10880
     * Exports the learning path as a SCORM package. This is the main function that
10881
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10882
     * whole thing and returns the zip.
10883
     *
10884
     * This method needs to be called in PHP5, as it will fail with non-adequate
10885
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10886
     * you need to call it on a learnpath object.
10887
     *
10888
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10889
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10890
     * path has been modified, it should use the generic method here below.
10891
     *
10892
     * @return string Returns the zip package string, or null if error
10893
     */
10894
    public function scormExport()
10895
    {
10896
        api_set_more_memory_and_time_limits();
10897
10898
        $_course = api_get_course_info();
10899
        $course_id = $_course['real_id'];
10900
        // Create the zip handler (this will remain available throughout the method).
10901
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10902
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10903
        $temp_dir_short = uniqid('scorm_export', true);
10904
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10905
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10906
        $zip_folder = new PclZip($temp_zip_file);
10907
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10908
        $root_path = $main_path = api_get_path(SYS_PATH);
10909
        $files_cleanup = [];
10910
10911
        // Place to temporarily stash the zip file.
10912
        // create the temp dir if it doesn't exist
10913
        // or do a cleanup before creating the zip file.
10914
        if (!is_dir($temp_zip_dir)) {
10915
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10916
        } else {
10917
            // Cleanup: Check the temp dir for old files and delete them.
10918
            $handle = opendir($temp_zip_dir);
10919
            while (false !== ($file = readdir($handle))) {
10920
                if ($file != '.' && $file != '..') {
10921
                    unlink("$temp_zip_dir/$file");
10922
                }
10923
            }
10924
            closedir($handle);
10925
        }
10926
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10927
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10928
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10929
        ) {
10930
            // Remove the possible . at the end of the path.
10931
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10932
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10933
            mkdir(
10934
                $dest_path_to_scorm_folder,
10935
                api_get_permissions_for_new_directories(),
10936
                true
10937
            );
10938
            copyr(
10939
                $current_course_path.'/scorm/'.$this->path,
10940
                $dest_path_to_scorm_folder,
10941
                ['imsmanifest'],
10942
                $zip_files
10943
            );
10944
        }
10945
10946
        // Build a dummy imsmanifest structure.
10947
        // Do not add to the zip yet (we still need it).
10948
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10949
        // Aggregation Model official document, section "2.3 Content Packaging".
10950
        // We are going to build a UTF-8 encoded manifest.
10951
        // Later we will recode it to the desired (and supported) encoding.
10952
        $xmldoc = new DOMDocument('1.0');
10953
        $root = $xmldoc->createElement('manifest');
10954
        $root->setAttribute('identifier', 'SingleCourseManifest');
10955
        $root->setAttribute('version', '1.1');
10956
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10957
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10958
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10959
        $root->setAttribute(
10960
            'xsi:schemaLocation',
10961
            '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'
10962
        );
10963
        // Build mandatory sub-root container elements.
10964
        $metadata = $xmldoc->createElement('metadata');
10965
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10966
        $metadata->appendChild($md_schema);
10967
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10968
        $metadata->appendChild($md_schemaversion);
10969
        $root->appendChild($metadata);
10970
10971
        $organizations = $xmldoc->createElement('organizations');
10972
        $resources = $xmldoc->createElement('resources');
10973
10974
        // Build the only organization we will use in building our learnpaths.
10975
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10976
        $organization = $xmldoc->createElement('organization');
10977
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10978
        // To set the title of the SCORM entity (=organization), we take the name given
10979
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10980
        // learning path charset) as it is the encoding that defines how it is stored
10981
        // in the database. Then we convert it to HTML entities again as the "&" character
10982
        // alone is not authorized in XML (must be &amp;).
10983
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10984
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10985
        $organization->appendChild($org_title);
10986
        $folder_name = 'document';
10987
10988
        // Removes the learning_path/scorm_folder path when exporting see #4841
10989
        $path_to_remove = '';
10990
        $path_to_replace = '';
10991
        $result = $this->generate_lp_folder($_course);
10992
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10993
            $path_to_remove = 'document'.$result['dir'];
10994
            $path_to_replace = $folder_name.'/';
10995
        }
10996
10997
        // Fixes chamilo scorm exports
10998
        if ($this->ref === 'chamilo_scorm_export') {
10999
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11000
        }
11001
11002
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11003
        $link_updates = [];
11004
        $links_to_create = [];
11005
        foreach ($this->ordered_items as $index => $itemId) {
11006
            /** @var learnpathItem $item */
11007
            $item = $this->items[$itemId];
11008
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11009
                // Get included documents from this item.
11010
                if ($item->type === 'sco') {
11011
                    $inc_docs = $item->get_resources_from_source(
11012
                        null,
11013
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11014
                    );
11015
                } else {
11016
                    $inc_docs = $item->get_resources_from_source();
11017
                }
11018
11019
                // Give a child element <item> to the <organization> element.
11020
                $my_item_id = $item->get_id();
11021
                $my_item = $xmldoc->createElement('item');
11022
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11023
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11024
                $my_item->setAttribute('isvisible', 'true');
11025
                // Give a child element <title> to the <item> element.
11026
                $my_title = $xmldoc->createElement(
11027
                    'title',
11028
                    htmlspecialchars(
11029
                        api_utf8_encode($item->get_title()),
11030
                        ENT_QUOTES,
11031
                        'UTF-8'
11032
                    )
11033
                );
11034
                $my_item->appendChild($my_title);
11035
                // Give a child element <adlcp:prerequisites> to the <item> element.
11036
                $my_prereqs = $xmldoc->createElement(
11037
                    'adlcp:prerequisites',
11038
                    $this->get_scorm_prereq_string($my_item_id)
11039
                );
11040
                $my_prereqs->setAttribute('type', 'aicc_script');
11041
                $my_item->appendChild($my_prereqs);
11042
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11043
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11044
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11045
                //$xmldoc->createElement('adlcp:timelimitaction','');
11046
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11047
                //$xmldoc->createElement('adlcp:datafromlms','');
11048
                // Give a child element <adlcp:masteryscore> to the <item> element.
11049
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11050
                $my_item->appendChild($my_masteryscore);
11051
11052
                // Attach this item to the organization element or hits parent if there is one.
11053
                if (!empty($item->parent) && $item->parent != 0) {
11054
                    $children = $organization->childNodes;
11055
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11056
                    if (is_object($possible_parent)) {
11057
                        $possible_parent->appendChild($my_item);
11058
                    } else {
11059
                        if ($this->debug > 0) {
11060
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11061
                        }
11062
                    }
11063
                } else {
11064
                    if ($this->debug > 0) {
11065
                        error_log('No parent');
11066
                    }
11067
                    $organization->appendChild($my_item);
11068
                }
11069
11070
                // Get the path of the file(s) from the course directory root.
11071
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11072
                $my_xml_file_path = $my_file_path;
11073
                if (!empty($path_to_remove)) {
11074
                    // From docs
11075
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11076
11077
                    // From quiz
11078
                    if ($this->ref === 'chamilo_scorm_export') {
11079
                        $path_to_remove = 'scorm/'.$this->path.'/';
11080
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11081
                    }
11082
                }
11083
11084
                $my_sub_dir = dirname($my_file_path);
11085
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11086
                $my_xml_sub_dir = $my_sub_dir;
11087
                // Give a <resource> child to the <resources> element
11088
                $my_resource = $xmldoc->createElement('resource');
11089
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11090
                $my_resource->setAttribute('type', 'webcontent');
11091
                $my_resource->setAttribute('href', $my_xml_file_path);
11092
                // adlcp:scormtype can be either 'sco' or 'asset'.
11093
                if ($item->type === 'sco') {
11094
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11095
                } else {
11096
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11097
                }
11098
                // xml:base is the base directory to find the files declared in this resource.
11099
                $my_resource->setAttribute('xml:base', '');
11100
                // Give a <file> child to the <resource> element.
11101
                $my_file = $xmldoc->createElement('file');
11102
                $my_file->setAttribute('href', $my_xml_file_path);
11103
                $my_resource->appendChild($my_file);
11104
11105
                // Dependency to other files - not yet supported.
11106
                $i = 1;
11107
                if ($inc_docs) {
11108
                    foreach ($inc_docs as $doc_info) {
11109
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11110
                            continue;
11111
                        }
11112
                        $my_dep = $xmldoc->createElement('resource');
11113
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11114
                        $my_dep->setAttribute('identifier', $res_id);
11115
                        $my_dep->setAttribute('type', 'webcontent');
11116
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11117
                        $my_dep_file = $xmldoc->createElement('file');
11118
                        // Check type of URL.
11119
                        if ($doc_info[1] == 'remote') {
11120
                            // Remote file. Save url as is.
11121
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11122
                            $my_dep->setAttribute('xml:base', '');
11123
                        } elseif ($doc_info[1] === 'local') {
11124
                            switch ($doc_info[2]) {
11125
                                case 'url':
11126
                                    // Local URL - save path as url for now, don't zip file.
11127
                                    $abs_path = api_get_path(SYS_PATH).
11128
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11129
                                    $current_dir = dirname($abs_path);
11130
                                    $current_dir = str_replace('\\', '/', $current_dir);
11131
                                    $file_path = realpath($abs_path);
11132
                                    $file_path = str_replace('\\', '/', $file_path);
11133
                                    $my_dep_file->setAttribute('href', $file_path);
11134
                                    $my_dep->setAttribute('xml:base', '');
11135
                                    if (strstr($file_path, $main_path) !== false) {
11136
                                        // The calculated real path is really inside Chamilo's root path.
11137
                                        // Reduce file path to what's under the DocumentRoot.
11138
                                        $replace = $file_path;
11139
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11140
                                        $destinationFile = $file_path;
11141
11142
                                        if (strstr($file_path, 'upload/users') !== false) {
11143
                                            $pos = strpos($file_path, 'my_files/');
11144
                                            if ($pos !== false) {
11145
                                                $onlyDirectory = str_replace(
11146
                                                    'upload/users/',
11147
                                                    '',
11148
                                                    substr($file_path, $pos, strlen($file_path))
11149
                                                );
11150
                                            }
11151
                                            $replace = $onlyDirectory;
11152
                                            $destinationFile = $replace;
11153
                                        }
11154
                                        $zip_files_abs[] = $file_path;
11155
                                        $link_updates[$my_file_path][] = [
11156
                                            'orig' => $doc_info[0],
11157
                                            'dest' => $destinationFile,
11158
                                            'replace' => $replace,
11159
                                        ];
11160
                                        $my_dep_file->setAttribute('href', $file_path);
11161
                                        $my_dep->setAttribute('xml:base', '');
11162
                                    } elseif (empty($file_path)) {
11163
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11164
                                        $file_path = str_replace('//', '/', $file_path);
11165
                                        if (file_exists($file_path)) {
11166
                                            // We get the relative path.
11167
                                            $file_path = substr($file_path, strlen($current_dir));
11168
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11169
                                            $link_updates[$my_file_path][] = [
11170
                                                'orig' => $doc_info[0],
11171
                                                'dest' => $file_path,
11172
                                            ];
11173
                                            $my_dep_file->setAttribute('href', $file_path);
11174
                                            $my_dep->setAttribute('xml:base', '');
11175
                                        }
11176
                                    }
11177
                                    break;
11178
                                case 'abs':
11179
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11180
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11181
                                    $my_dep->setAttribute('xml:base', '');
11182
11183
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11184
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11185
                                    $abs_img_path_without_subdir = $doc_info[0];
11186
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11187
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11188
                                    if ($pos === 0) {
11189
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11190
                                    }
11191
11192
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11193
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11194
11195
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11196
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11197
                                    // Check if the current document is in that path.
11198
                                    if (strstr($file_path, $cur_path) !== false) {
11199
                                        $destinationFile = substr($file_path, strlen($cur_path));
11200
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11201
11202
                                        $fileToTest = $cur_path.$my_file_path;
11203
                                        if (!empty($path_to_remove)) {
11204
                                            $fileToTest = str_replace(
11205
                                                $path_to_remove.'/',
11206
                                                $path_to_replace,
11207
                                                $cur_path.$my_file_path
11208
                                            );
11209
                                        }
11210
11211
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11212
11213
                                        // Put the current document in the zip (this array is the array
11214
                                        // that will manage documents already in the course folder - relative).
11215
                                        $zip_files[] = $filePathNoCoursePart;
11216
                                        // Update the links to the current document in the
11217
                                        // containing document (make them relative).
11218
                                        $link_updates[$my_file_path][] = [
11219
                                            'orig' => $doc_info[0],
11220
                                            'dest' => $destinationFile,
11221
                                            'replace' => $relative_path,
11222
                                        ];
11223
11224
                                        $my_dep_file->setAttribute('href', $file_path);
11225
                                        $my_dep->setAttribute('xml:base', '');
11226
                                    } elseif (strstr($file_path, $main_path) !== false) {
11227
                                        // The calculated real path is really inside Chamilo's root path.
11228
                                        // Reduce file path to what's under the DocumentRoot.
11229
                                        $file_path = substr($file_path, strlen($root_path));
11230
                                        $zip_files_abs[] = $file_path;
11231
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11232
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11233
                                        $my_dep->setAttribute('xml:base', '');
11234
                                    } elseif (empty($file_path)) {
11235
                                        // Probably this is an image inside "/main" directory
11236
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11237
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11238
11239
                                        if (file_exists($file_path)) {
11240
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11241
                                                // We get the relative path.
11242
                                                $pos = strpos($file_path, 'main/default_course_document/');
11243
                                                if ($pos !== false) {
11244
                                                    $onlyDirectory = str_replace(
11245
                                                        'main/default_course_document/',
11246
                                                        '',
11247
                                                        substr($file_path, $pos, strlen($file_path))
11248
                                                    );
11249
                                                }
11250
11251
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11252
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11253
                                                $zip_files_abs[] = $fileAbs;
11254
                                                $link_updates[$my_file_path][] = [
11255
                                                    'orig' => $doc_info[0],
11256
                                                    'dest' => $destinationFile,
11257
                                                ];
11258
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11259
                                                $my_dep->setAttribute('xml:base', '');
11260
                                            }
11261
                                        }
11262
                                    }
11263
                                    break;
11264
                                case 'rel':
11265
                                    // Path relative to the current document.
11266
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11267
                                    if (substr($doc_info[0], 0, 2) === '..') {
11268
                                        // Relative path going up.
11269
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11270
                                        $current_dir = str_replace('\\', '/', $current_dir);
11271
                                        $file_path = realpath($current_dir.$doc_info[0]);
11272
                                        $file_path = str_replace('\\', '/', $file_path);
11273
                                        if (strstr($file_path, $main_path) !== false) {
11274
                                            // The calculated real path is really inside Chamilo's root path.
11275
                                            // Reduce file path to what's under the DocumentRoot.
11276
                                            $file_path = substr($file_path, strlen($root_path));
11277
                                            $zip_files_abs[] = $file_path;
11278
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11279
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11280
                                            $my_dep->setAttribute('xml:base', '');
11281
                                        }
11282
                                    } else {
11283
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11284
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11285
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11286
                                    }
11287
                                    break;
11288
                                default:
11289
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11290
                                    $my_dep->setAttribute('xml:base', '');
11291
                                    break;
11292
                            }
11293
                        }
11294
                        $my_dep->appendChild($my_dep_file);
11295
                        $resources->appendChild($my_dep);
11296
                        $dependency = $xmldoc->createElement('dependency');
11297
                        $dependency->setAttribute('identifierref', $res_id);
11298
                        $my_resource->appendChild($dependency);
11299
                        $i++;
11300
                    }
11301
                }
11302
                $resources->appendChild($my_resource);
11303
                $zip_files[] = $my_file_path;
11304
            } else {
11305
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11306
                switch ($item->type) {
11307
                    case TOOL_LINK:
11308
                        $my_item = $xmldoc->createElement('item');
11309
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11310
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11311
                        $my_item->setAttribute('isvisible', 'true');
11312
                        // Give a child element <title> to the <item> element.
11313
                        $my_title = $xmldoc->createElement(
11314
                            'title',
11315
                            htmlspecialchars(
11316
                                api_utf8_encode($item->get_title()),
11317
                                ENT_QUOTES,
11318
                                'UTF-8'
11319
                            )
11320
                        );
11321
                        $my_item->appendChild($my_title);
11322
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11323
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11324
                        $my_prereqs->setAttribute('type', 'aicc_script');
11325
                        $my_item->appendChild($my_prereqs);
11326
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11327
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11328
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11329
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11330
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11331
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11332
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11333
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11334
                        $my_item->appendChild($my_masteryscore);
11335
11336
                        // Attach this item to the organization element or its parent if there is one.
11337
                        if (!empty($item->parent) && $item->parent != 0) {
11338
                            $children = $organization->childNodes;
11339
                            for ($i = 0; $i < $children->length; $i++) {
11340
                                $item_temp = $children->item($i);
11341
                                if ($item_temp->nodeName == 'item') {
11342
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11343
                                        $item_temp->appendChild($my_item);
11344
                                    }
11345
                                }
11346
                            }
11347
                        } else {
11348
                            $organization->appendChild($my_item);
11349
                        }
11350
11351
                        $my_file_path = 'link_'.$item->get_id().'.html';
11352
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11353
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11354
                        $rs = Database::query($sql);
11355
                        if ($link = Database::fetch_array($rs)) {
11356
                            $url = $link['url'];
11357
                            $title = stripslashes($link['title']);
11358
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11359
                            $my_xml_file_path = $my_file_path;
11360
                            $my_sub_dir = dirname($my_file_path);
11361
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11362
                            $my_xml_sub_dir = $my_sub_dir;
11363
                            // Give a <resource> child to the <resources> element.
11364
                            $my_resource = $xmldoc->createElement('resource');
11365
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11366
                            $my_resource->setAttribute('type', 'webcontent');
11367
                            $my_resource->setAttribute('href', $my_xml_file_path);
11368
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11369
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11370
                            // xml:base is the base directory to find the files declared in this resource.
11371
                            $my_resource->setAttribute('xml:base', '');
11372
                            // give a <file> child to the <resource> element.
11373
                            $my_file = $xmldoc->createElement('file');
11374
                            $my_file->setAttribute('href', $my_xml_file_path);
11375
                            $my_resource->appendChild($my_file);
11376
                            $resources->appendChild($my_resource);
11377
                        }
11378
                        break;
11379
                    case TOOL_QUIZ:
11380
                        $exe_id = $item->path;
11381
                        // Should be using ref when everything will be cleaned up in this regard.
11382
                        $exe = new Exercise();
11383
                        $exe->read($exe_id);
11384
                        $my_item = $xmldoc->createElement('item');
11385
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11386
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11387
                        $my_item->setAttribute('isvisible', 'true');
11388
                        // Give a child element <title> to the <item> element.
11389
                        $my_title = $xmldoc->createElement(
11390
                            'title',
11391
                            htmlspecialchars(
11392
                                api_utf8_encode($item->get_title()),
11393
                                ENT_QUOTES,
11394
                                'UTF-8'
11395
                            )
11396
                        );
11397
                        $my_item->appendChild($my_title);
11398
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11399
                        $my_item->appendChild($my_max_score);
11400
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11401
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11402
                        $my_prereqs->setAttribute('type', 'aicc_script');
11403
                        $my_item->appendChild($my_prereqs);
11404
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11405
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11406
                        $my_item->appendChild($my_masteryscore);
11407
11408
                        // Attach this item to the organization element or hits parent if there is one.
11409
                        if (!empty($item->parent) && $item->parent != 0) {
11410
                            $children = $organization->childNodes;
11411
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11412
                            if ($possible_parent) {
11413
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11414
                                    $possible_parent->appendChild($my_item);
11415
                                }
11416
                            }
11417
                        } else {
11418
                            $organization->appendChild($my_item);
11419
                        }
11420
11421
                        // Get the path of the file(s) from the course directory root
11422
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11423
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11424
                        // Write the contents of the exported exercise into a (big) html file
11425
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11426
                        $scormExercise = new ScormExercise($exe, true);
11427
                        $contents = $scormExercise->export();
11428
11429
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11430
                        $res = file_put_contents($tmp_file_path, $contents);
11431
                        if ($res === false) {
11432
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11433
                        }
11434
                        $files_cleanup[] = $tmp_file_path;
11435
                        $my_xml_file_path = $my_file_path;
11436
                        $my_sub_dir = dirname($my_file_path);
11437
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11438
                        $my_xml_sub_dir = $my_sub_dir;
11439
                        // Give a <resource> child to the <resources> element.
11440
                        $my_resource = $xmldoc->createElement('resource');
11441
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11442
                        $my_resource->setAttribute('type', 'webcontent');
11443
                        $my_resource->setAttribute('href', $my_xml_file_path);
11444
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11445
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11446
                        // xml:base is the base directory to find the files declared in this resource.
11447
                        $my_resource->setAttribute('xml:base', '');
11448
                        // Give a <file> child to the <resource> element.
11449
                        $my_file = $xmldoc->createElement('file');
11450
                        $my_file->setAttribute('href', $my_xml_file_path);
11451
                        $my_resource->appendChild($my_file);
11452
11453
                        // Get included docs.
11454
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11455
11456
                        // Dependency to other files - not yet supported.
11457
                        $i = 1;
11458
                        foreach ($inc_docs as $doc_info) {
11459
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11460
                                continue;
11461
                            }
11462
                            $my_dep = $xmldoc->createElement('resource');
11463
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11464
                            $my_dep->setAttribute('identifier', $res_id);
11465
                            $my_dep->setAttribute('type', 'webcontent');
11466
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11467
                            $my_dep_file = $xmldoc->createElement('file');
11468
                            // Check type of URL.
11469
                            if ($doc_info[1] == 'remote') {
11470
                                // Remote file. Save url as is.
11471
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11472
                                $my_dep->setAttribute('xml:base', '');
11473
                            } elseif ($doc_info[1] == 'local') {
11474
                                switch ($doc_info[2]) {
11475
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11476
                                        // Save file but as local file (retrieve from URL).
11477
                                        $abs_path = api_get_path(SYS_PATH).
11478
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11479
                                        $current_dir = dirname($abs_path);
11480
                                        $current_dir = str_replace('\\', '/', $current_dir);
11481
                                        $file_path = realpath($abs_path);
11482
                                        $file_path = str_replace('\\', '/', $file_path);
11483
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11484
                                        $my_dep->setAttribute('xml:base', '');
11485
                                        if (strstr($file_path, $main_path) !== false) {
11486
                                            // The calculated real path is really inside the chamilo root path.
11487
                                            // Reduce file path to what's under the DocumentRoot.
11488
                                            $file_path = substr($file_path, strlen($root_path));
11489
                                            $zip_files_abs[] = $file_path;
11490
                                            $link_updates[$my_file_path][] = [
11491
                                                'orig' => $doc_info[0],
11492
                                                'dest' => 'document/'.$file_path,
11493
                                            ];
11494
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11495
                                            $my_dep->setAttribute('xml:base', '');
11496
                                        } elseif (empty($file_path)) {
11497
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11498
                                            $file_path = str_replace('//', '/', $file_path);
11499
                                            if (file_exists($file_path)) {
11500
                                                $file_path = substr($file_path, strlen($current_dir));
11501
                                                // We get the relative path.
11502
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11503
                                                $link_updates[$my_file_path][] = [
11504
                                                    'orig' => $doc_info[0],
11505
                                                    'dest' => 'document/'.$file_path,
11506
                                                ];
11507
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11508
                                                $my_dep->setAttribute('xml:base', '');
11509
                                            }
11510
                                        }
11511
                                        break;
11512
                                    case 'abs':
11513
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11514
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11515
                                        $current_dir = str_replace('\\', '/', $current_dir);
11516
                                        $file_path = realpath($doc_info[0]);
11517
                                        $file_path = str_replace('\\', '/', $file_path);
11518
                                        $my_dep_file->setAttribute('href', $file_path);
11519
                                        $my_dep->setAttribute('xml:base', '');
11520
11521
                                        if (strstr($file_path, $main_path) !== false) {
11522
                                            // The calculated real path is really inside the chamilo root path.
11523
                                            // Reduce file path to what's under the DocumentRoot.
11524
                                            $file_path = substr($file_path, strlen($root_path));
11525
                                            $zip_files_abs[] = $file_path;
11526
                                            $link_updates[$my_file_path][] = [
11527
                                                'orig' => $doc_info[0],
11528
                                                'dest' => $file_path,
11529
                                            ];
11530
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11531
                                            $my_dep->setAttribute('xml:base', '');
11532
                                        } elseif (empty($file_path)) {
11533
                                            $docSysPartPath = str_replace(
11534
                                                api_get_path(REL_COURSE_PATH),
11535
                                                '',
11536
                                                $doc_info[0]
11537
                                            );
11538
11539
                                            $docSysPartPathNoCourseCode = str_replace(
11540
                                                $_course['directory'].'/',
11541
                                                '',
11542
                                                $docSysPartPath
11543
                                            );
11544
11545
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11546
                                            if (file_exists($docSysPath)) {
11547
                                                $file_path = $docSysPartPathNoCourseCode;
11548
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11549
                                                $link_updates[$my_file_path][] = [
11550
                                                    'orig' => $doc_info[0],
11551
                                                    'dest' => $file_path,
11552
                                                ];
11553
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11554
                                                $my_dep->setAttribute('xml:base', '');
11555
                                            }
11556
                                        }
11557
                                        break;
11558
                                    case 'rel':
11559
                                        // Path relative to the current document. Save xml:base as current document's
11560
                                        // directory and save file in zip as subdir.file_path
11561
                                        if (substr($doc_info[0], 0, 2) === '..') {
11562
                                            // Relative path going up.
11563
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11564
                                            $current_dir = str_replace('\\', '/', $current_dir);
11565
                                            $file_path = realpath($current_dir.$doc_info[0]);
11566
                                            $file_path = str_replace('\\', '/', $file_path);
11567
                                            if (strstr($file_path, $main_path) !== false) {
11568
                                                // The calculated real path is really inside Chamilo's root path.
11569
                                                // Reduce file path to what's under the DocumentRoot.
11570
11571
                                                $file_path = substr($file_path, strlen($root_path));
11572
                                                $file_path_dest = $file_path;
11573
11574
                                                // File path is courses/CHAMILO/document/....
11575
                                                $info_file_path = explode('/', $file_path);
11576
                                                if ($info_file_path[0] == 'courses') {
11577
                                                    // Add character "/" in file path.
11578
                                                    $file_path_dest = 'document/'.$file_path;
11579
                                                }
11580
                                                $zip_files_abs[] = $file_path;
11581
11582
                                                $link_updates[$my_file_path][] = [
11583
                                                    'orig' => $doc_info[0],
11584
                                                    'dest' => $file_path_dest,
11585
                                                ];
11586
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11587
                                                $my_dep->setAttribute('xml:base', '');
11588
                                            }
11589
                                        } else {
11590
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11591
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11592
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11593
                                        }
11594
                                        break;
11595
                                    default:
11596
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11597
                                        $my_dep->setAttribute('xml:base', '');
11598
                                        break;
11599
                                }
11600
                            }
11601
                            $my_dep->appendChild($my_dep_file);
11602
                            $resources->appendChild($my_dep);
11603
                            $dependency = $xmldoc->createElement('dependency');
11604
                            $dependency->setAttribute('identifierref', $res_id);
11605
                            $my_resource->appendChild($dependency);
11606
                            $i++;
11607
                        }
11608
                        $resources->appendChild($my_resource);
11609
                        $zip_files[] = $my_file_path;
11610
                        break;
11611
                    default:
11612
                        // Get the path of the file(s) from the course directory root
11613
                        $my_file_path = 'non_exportable.html';
11614
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11615
                        $my_xml_file_path = $my_file_path;
11616
                        $my_sub_dir = dirname($my_file_path);
11617
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11618
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11619
                        $my_xml_sub_dir = $my_sub_dir;
11620
                        // Give a <resource> child to the <resources> element.
11621
                        $my_resource = $xmldoc->createElement('resource');
11622
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11623
                        $my_resource->setAttribute('type', 'webcontent');
11624
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11625
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11626
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11627
                        // xml:base is the base directory to find the files declared in this resource.
11628
                        $my_resource->setAttribute('xml:base', '');
11629
                        // Give a <file> child to the <resource> element.
11630
                        $my_file = $xmldoc->createElement('file');
11631
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11632
                        $my_resource->appendChild($my_file);
11633
                        $resources->appendChild($my_resource);
11634
                        break;
11635
                }
11636
            }
11637
        }
11638
        $organizations->appendChild($organization);
11639
        $root->appendChild($organizations);
11640
        $root->appendChild($resources);
11641
        $xmldoc->appendChild($root);
11642
11643
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11644
11645
        // then add the file to the zip, then destroy the file (this is done automatically).
11646
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11647
        foreach ($zip_files as $file_path) {
11648
            if (empty($file_path)) {
11649
                continue;
11650
            }
11651
11652
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11653
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11654
11655
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11656
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11657
            }
11658
11659
            $this->create_path($dest_file);
11660
            @copy($filePath, $dest_file);
11661
11662
            // Check if the file needs a link update.
11663
            if (in_array($file_path, array_keys($link_updates))) {
11664
                $string = file_get_contents($dest_file);
11665
                unlink($dest_file);
11666
                foreach ($link_updates[$file_path] as $old_new) {
11667
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11668
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11669
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11670
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11671
                    if (substr($old_new['dest'], -3) === 'flv' &&
11672
                        substr($old_new['dest'], 0, 5) === 'main/'
11673
                    ) {
11674
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11675
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11676
                        substr($old_new['dest'], 0, 6) === 'video/'
11677
                    ) {
11678
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11679
                    }
11680
11681
                    // Fix to avoid problems with default_course_document
11682
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11683
                        $newDestination = $old_new['dest'];
11684
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11685
                            $newDestination = $old_new['replace'];
11686
                        }
11687
                    } else {
11688
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11689
                    }
11690
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11691
11692
                    // Add files inside the HTMLs
11693
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11694
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11695
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11696
                        copy(
11697
                            $sys_course_path.$new_path,
11698
                            $destinationFile
11699
                        );
11700
                    }
11701
                }
11702
                file_put_contents($dest_file, $string);
11703
            }
11704
11705
            if (file_exists($filePath) && $copyAll) {
11706
                $extension = $this->get_extension($filePath);
11707
                if (in_array($extension, ['html', 'html'])) {
11708
                    $containerOrigin = dirname($filePath);
11709
                    $containerDestination = dirname($dest_file);
11710
11711
                    $finder = new Finder();
11712
                    $finder->files()->in($containerOrigin)
11713
                        ->notName('*_DELETED_*')
11714
                        ->exclude('share_folder')
11715
                        ->exclude('chat_files')
11716
                        ->exclude('certificates')
11717
                    ;
11718
11719
                    if (is_dir($containerOrigin) &&
11720
                        is_dir($containerDestination)
11721
                    ) {
11722
                        $fs = new Filesystem();
11723
                        $fs->mirror(
11724
                            $containerOrigin,
11725
                            $containerDestination,
11726
                            $finder
11727
                        );
11728
                    }
11729
                }
11730
            }
11731
        }
11732
11733
        foreach ($zip_files_abs as $file_path) {
11734
            if (empty($file_path)) {
11735
                continue;
11736
            }
11737
11738
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11739
                continue;
11740
            }
11741
11742
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11743
            if (strstr($file_path, 'upload/users') !== false) {
11744
                $pos = strpos($file_path, 'my_files/');
11745
                if ($pos !== false) {
11746
                    $onlyDirectory = str_replace(
11747
                        'upload/users/',
11748
                        '',
11749
                        substr($file_path, $pos, strlen($file_path))
11750
                    );
11751
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11752
                }
11753
            }
11754
11755
            if (strstr($file_path, 'default_course_document/') !== false) {
11756
                $replace = str_replace('/main', '', $file_path);
11757
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11758
            }
11759
11760
            if (empty($dest_file)) {
11761
                continue;
11762
            }
11763
11764
            $this->create_path($dest_file);
11765
            copy($main_path.$file_path, $dest_file);
11766
            // Check if the file needs a link update.
11767
            if (in_array($file_path, array_keys($link_updates))) {
11768
                $string = file_get_contents($dest_file);
11769
                unlink($dest_file);
11770
                foreach ($link_updates[$file_path] as $old_new) {
11771
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11772
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11773
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11774
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11775
                    if (substr($old_new['dest'], -3) == 'flv' &&
11776
                        substr($old_new['dest'], 0, 5) == 'main/'
11777
                    ) {
11778
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11779
                    }
11780
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11781
                }
11782
                file_put_contents($dest_file, $string);
11783
            }
11784
        }
11785
11786
        if (is_array($links_to_create)) {
11787
            foreach ($links_to_create as $file => $link) {
11788
                $content = '<!DOCTYPE html><head>
11789
                            <meta charset="'.api_get_language_isocode().'" />
11790
                            <title>'.$link['title'].'</title>
11791
                            </head>
11792
                            <body dir="'.api_get_text_direction().'">
11793
                            <div style="text-align:center">
11794
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11795
                            </body>
11796
                            </html>';
11797
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11798
            }
11799
        }
11800
11801
        // Add non exportable message explanation.
11802
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11803
        $file_content = '<!DOCTYPE html><head>
11804
                        <meta charset="'.api_get_language_isocode().'" />
11805
                        <title>'.$lang_not_exportable.'</title>
11806
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11807
                        </head>
11808
                        <body dir="'.api_get_text_direction().'">';
11809
        $file_content .=
11810
            <<<EOD
11811
                    <style>
11812
            .error-message {
11813
                font-family: arial, verdana, helvetica, sans-serif;
11814
                border-width: 1px;
11815
                border-style: solid;
11816
                left: 50%;
11817
                margin: 10px auto;
11818
                min-height: 30px;
11819
                padding: 5px;
11820
                right: 50%;
11821
                width: 500px;
11822
                background-color: #FFD1D1;
11823
                border-color: #FF0000;
11824
                color: #000;
11825
            }
11826
        </style>
11827
    <body>
11828
        <div class="error-message">
11829
            $lang_not_exportable
11830
        </div>
11831
    </body>
11832
</html>
11833
EOD;
11834
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11835
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11836
        }
11837
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11838
11839
        // Add the extra files that go along with a SCORM package.
11840
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11841
11842
        $fs = new Filesystem();
11843
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11844
11845
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11846
        $manifest = @$xmldoc->saveXML();
11847
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11848
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11849
        $zip_folder->add(
11850
            $archivePath.'/'.$temp_dir_short,
11851
            PCLZIP_OPT_REMOVE_PATH,
11852
            $archivePath.'/'.$temp_dir_short.'/'
11853
        );
11854
11855
        // Clean possible temporary files.
11856
        foreach ($files_cleanup as $file) {
11857
            $res = unlink($file);
11858
            if ($res === false) {
11859
                error_log(
11860
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11861
                    0
11862
                );
11863
            }
11864
        }
11865
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11866
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11867
    }
11868
11869
    /**
11870
     * @param int $lp_id
11871
     *
11872
     * @return bool
11873
     */
11874
    public function scorm_export_to_pdf($lp_id)
11875
    {
11876
        $lp_id = (int) $lp_id;
11877
        $files_to_export = [];
11878
11879
        $sessionId = api_get_session_id();
11880
        $course_data = api_get_course_info($this->cc);
11881
11882
        if (!empty($course_data)) {
11883
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11884
            $list = self::get_flat_ordered_items_list($lp_id);
11885
            if (!empty($list)) {
11886
                foreach ($list as $item_id) {
11887
                    $item = $this->items[$item_id];
11888
                    switch ($item->type) {
11889
                        case 'document':
11890
                            // Getting documents from a LP with chamilo documents
11891
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11892
                            // Try loading document from the base course.
11893
                            if (empty($file_data) && !empty($sessionId)) {
11894
                                $file_data = DocumentManager::get_document_data_by_id(
11895
                                    $item->path,
11896
                                    $this->cc,
11897
                                    false,
11898
                                    0
11899
                                );
11900
                            }
11901
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11902
                            if (file_exists($file_path)) {
11903
                                $files_to_export[] = [
11904
                                    'title' => $item->get_title(),
11905
                                    'path' => $file_path,
11906
                                ];
11907
                            }
11908
                            break;
11909
                        case 'asset': //commes from a scorm package generated by chamilo
11910
                        case 'sco':
11911
                            $file_path = $scorm_path.'/'.$item->path;
11912
                            if (file_exists($file_path)) {
11913
                                $files_to_export[] = [
11914
                                    'title' => $item->get_title(),
11915
                                    'path' => $file_path,
11916
                                ];
11917
                            }
11918
                            break;
11919
                        case 'dir':
11920
                            $files_to_export[] = [
11921
                                'title' => $item->get_title(),
11922
                                'path' => null,
11923
                            ];
11924
                            break;
11925
                    }
11926
                }
11927
            }
11928
11929
            $pdf = new PDF();
11930
            $result = $pdf->html_to_pdf(
11931
                $files_to_export,
11932
                $this->name,
11933
                $this->cc,
11934
                true,
11935
                true,
11936
                true,
11937
                $this->get_name()
11938
            );
11939
11940
            return $result;
11941
        }
11942
11943
        return false;
11944
    }
11945
11946
    /**
11947
     * Temp function to be moved in main_api or the best place around for this.
11948
     * Creates a file path if it doesn't exist.
11949
     *
11950
     * @param string $path
11951
     */
11952
    public function create_path($path)
11953
    {
11954
        $path_bits = explode('/', dirname($path));
11955
11956
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11957
        $path_built = IS_WINDOWS_OS ? '' : '/';
11958
        foreach ($path_bits as $bit) {
11959
            if (!empty($bit)) {
11960
                $new_path = $path_built.$bit;
11961
                if (is_dir($new_path)) {
11962
                    $path_built = $new_path.'/';
11963
                } else {
11964
                    mkdir($new_path, api_get_permissions_for_new_directories());
11965
                    $path_built = $new_path.'/';
11966
                }
11967
            }
11968
        }
11969
    }
11970
11971
    /**
11972
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11973
     *
11974
     * @return bool The results of the unlink function, or false if there was no image to start with
11975
     */
11976
    public function delete_lp_image()
11977
    {
11978
        $img = $this->get_preview_image();
11979
        if ($img != '') {
11980
            $del_file = $this->get_preview_image_path(null, 'sys');
11981
            if (isset($del_file) && file_exists($del_file)) {
11982
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11983
                if (file_exists($del_file_2)) {
11984
                    unlink($del_file_2);
11985
                }
11986
                $this->set_preview_image('');
11987
11988
                return @unlink($del_file);
11989
            }
11990
        }
11991
11992
        return false;
11993
    }
11994
11995
    /**
11996
     * Uploads an author image to the upload/learning_path/images directory.
11997
     *
11998
     * @param array    The image array, coming from the $_FILES superglobal
11999
     *
12000
     * @return bool True on success, false on error
12001
     */
12002
    public function upload_image($image_array)
12003
    {
12004
        if (!empty($image_array['name'])) {
12005
            $upload_ok = process_uploaded_file($image_array);
12006
            $has_attachment = true;
12007
        }
12008
12009
        if ($upload_ok && $has_attachment) {
12010
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12011
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12012
            $updir = $sys_course_path.$courseDir;
12013
            // Try to add an extension to the file if it hasn't one.
12014
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12015
12016
            if (filter_extension($new_file_name)) {
12017
                $file_extension = explode('.', $image_array['name']);
12018
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12019
                $filename = uniqid('');
12020
                $new_file_name = $filename.'.'.$file_extension;
12021
                $new_path = $updir.'/'.$new_file_name;
12022
12023
                // Resize the image.
12024
                $temp = new Image($image_array['tmp_name']);
12025
                $temp->resize(104);
12026
                $result = $temp->send_image($new_path);
12027
12028
                // Storing the image filename.
12029
                if ($result) {
12030
                    $this->set_preview_image($new_file_name);
12031
12032
                    //Resize to 64px to use on course homepage
12033
                    $temp->resize(64);
12034
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12035
12036
                    return true;
12037
                }
12038
            }
12039
        }
12040
12041
        return false;
12042
    }
12043
12044
    /**
12045
     * @param int    $lp_id
12046
     * @param string $status
12047
     */
12048
    public function set_autolaunch($lp_id, $status)
12049
    {
12050
        $course_id = api_get_course_int_id();
12051
        $lp_id = (int) $lp_id;
12052
        $status = (int) $status;
12053
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12054
12055
        // Setting everything to autolaunch = 0
12056
        $attributes['autolaunch'] = 0;
12057
        $where = [
12058
            'session_id = ? AND c_id = ? ' => [
12059
                api_get_session_id(),
12060
                $course_id,
12061
            ],
12062
        ];
12063
        Database::update($lp_table, $attributes, $where);
12064
        if ($status == 1) {
12065
            //Setting my lp_id to autolaunch = 1
12066
            $attributes['autolaunch'] = 1;
12067
            $where = [
12068
                'iid = ? AND session_id = ? AND c_id = ?' => [
12069
                    $lp_id,
12070
                    api_get_session_id(),
12071
                    $course_id,
12072
                ],
12073
            ];
12074
            Database::update($lp_table, $attributes, $where);
12075
        }
12076
    }
12077
12078
    /**
12079
     * Gets previous_item_id for the next element of the lp_item table.
12080
     *
12081
     * @author Isaac flores paz
12082
     *
12083
     * @return int Previous item ID
12084
     */
12085
    public function select_previous_item_id()
12086
    {
12087
        $course_id = api_get_course_int_id();
12088
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12089
12090
        // Get the max order of the items
12091
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12092
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12093
        $rs_max_order = Database::query($sql);
12094
        $row_max_order = Database::fetch_object($rs_max_order);
12095
        $max_order = $row_max_order->display_order;
12096
        // Get the previous item ID
12097
        $sql = "SELECT iid as previous FROM $table_lp_item
12098
                WHERE 
12099
                    c_id = $course_id AND 
12100
                    lp_id = ".$this->lp_id." AND 
12101
                    display_order = '$max_order' ";
12102
        $rs_max = Database::query($sql);
12103
        $row_max = Database::fetch_object($rs_max);
12104
12105
        // Return the previous item ID
12106
        return $row_max->previous;
12107
    }
12108
12109
    /**
12110
     * Copies an LP.
12111
     */
12112
    public function copy()
12113
    {
12114
        // Course builder
12115
        $cb = new CourseBuilder();
12116
12117
        //Setting tools that will be copied
12118
        $cb->set_tools_to_build(['learnpaths']);
12119
12120
        //Setting elements that will be copied
12121
        $cb->set_tools_specific_id_list(
12122
            ['learnpaths' => [$this->lp_id]]
12123
        );
12124
12125
        $course = $cb->build();
12126
12127
        //Course restorer
12128
        $course_restorer = new CourseRestorer($course);
12129
        $course_restorer->set_add_text_in_items(true);
12130
        $course_restorer->set_tool_copy_settings(
12131
            ['learnpaths' => ['reset_dates' => true]]
12132
        );
12133
        $course_restorer->restore(
12134
            api_get_course_id(),
12135
            api_get_session_id(),
12136
            false,
12137
            false
12138
        );
12139
    }
12140
12141
    /**
12142
     * Verify document size.
12143
     *
12144
     * @param string $s
12145
     *
12146
     * @return bool
12147
     */
12148
    public static function verify_document_size($s)
12149
    {
12150
        $post_max = ini_get('post_max_size');
12151
        if (substr($post_max, -1, 1) == 'M') {
12152
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12153
        } elseif (substr($post_max, -1, 1) == 'G') {
12154
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12155
        }
12156
        $upl_max = ini_get('upload_max_filesize');
12157
        if (substr($upl_max, -1, 1) == 'M') {
12158
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12159
        } elseif (substr($upl_max, -1, 1) == 'G') {
12160
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12161
        }
12162
        $documents_total_space = DocumentManager::documents_total_space();
12163
        $course_max_space = DocumentManager::get_course_quota();
12164
        $total_size = filesize($s) + $documents_total_space;
12165
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12166
            return true;
12167
        }
12168
12169
        return false;
12170
    }
12171
12172
    /**
12173
     * Clear LP prerequisites.
12174
     */
12175
    public function clear_prerequisites()
12176
    {
12177
        $course_id = $this->get_course_int_id();
12178
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12179
        $lp_id = $this->get_id();
12180
        // Cleaning prerequisites
12181
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12182
                WHERE c_id = $course_id AND lp_id = $lp_id";
12183
        Database::query($sql);
12184
12185
        // Cleaning mastery score for exercises
12186
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12187
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12188
        Database::query($sql);
12189
    }
12190
12191
    public function set_previous_step_as_prerequisite_for_all_items()
12192
    {
12193
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12194
        $course_id = $this->get_course_int_id();
12195
        $lp_id = $this->get_id();
12196
12197
        if (!empty($this->items)) {
12198
            $previous_item_id = null;
12199
            $previous_item_max = 0;
12200
            $previous_item_type = null;
12201
            $last_item_not_dir = null;
12202
            $last_item_not_dir_type = null;
12203
            $last_item_not_dir_max = null;
12204
12205
            foreach ($this->ordered_items as $itemId) {
12206
                $item = $this->getItem($itemId);
12207
                // if there was a previous item... (otherwise jump to set it)
12208
                if (!empty($previous_item_id)) {
12209
                    $current_item_id = $item->get_id(); //save current id
12210
                    if ($item->get_type() != 'dir') {
12211
                        // Current item is not a folder, so it qualifies to get a prerequisites
12212
                        if ($last_item_not_dir_type == 'quiz') {
12213
                            // if previous is quiz, mark its max score as default score to be achieved
12214
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12215
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12216
                            Database::query($sql);
12217
                        }
12218
                        // now simply update the prerequisite to set it to the last non-chapter item
12219
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12220
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12221
                        Database::query($sql);
12222
                        // record item as 'non-chapter' reference
12223
                        $last_item_not_dir = $item->get_id();
12224
                        $last_item_not_dir_type = $item->get_type();
12225
                        $last_item_not_dir_max = $item->get_max();
12226
                    }
12227
                } else {
12228
                    if ($item->get_type() != 'dir') {
12229
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12230
                        $last_item_not_dir = $item->get_id();
12231
                        $last_item_not_dir_type = $item->get_type();
12232
                        $last_item_not_dir_max = $item->get_max();
12233
                    }
12234
                }
12235
                // Saving the item as "previous item" for the next loop
12236
                $previous_item_id = $item->get_id();
12237
                $previous_item_max = $item->get_max();
12238
                $previous_item_type = $item->get_type();
12239
            }
12240
        }
12241
    }
12242
12243
    /**
12244
     * @param array $params
12245
     *
12246
     * @throws \Doctrine\ORM\OptimisticLockException
12247
     *
12248
     * @return int
12249
     */
12250
    public static function createCategory($params)
12251
    {
12252
        $em = Database::getManager();
12253
        $item = new CLpCategory();
12254
        $item->setName($params['name']);
12255
        $item->setCId($params['c_id']);
12256
        $em->persist($item);
12257
        $em->flush();
12258
12259
        api_item_property_update(
12260
            api_get_course_info(),
12261
            TOOL_LEARNPATH_CATEGORY,
12262
            $item->getId(),
12263
            'visible',
12264
            api_get_user_id()
12265
        );
12266
12267
        return $item->getId();
12268
    }
12269
12270
    /**
12271
     * @param array $params
12272
     *
12273
     * @throws \Doctrine\ORM\ORMException
12274
     * @throws \Doctrine\ORM\OptimisticLockException
12275
     * @throws \Doctrine\ORM\TransactionRequiredException
12276
     */
12277
    public static function updateCategory($params)
12278
    {
12279
        $em = Database::getManager();
12280
        /** @var CLpCategory $item */
12281
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12282
        if ($item) {
12283
            $item->setName($params['name']);
12284
            $em->merge($item);
12285
            $em->flush();
12286
        }
12287
    }
12288
12289
    /**
12290
     * @param int $id
12291
     *
12292
     * @throws \Doctrine\ORM\ORMException
12293
     * @throws \Doctrine\ORM\OptimisticLockException
12294
     * @throws \Doctrine\ORM\TransactionRequiredException
12295
     */
12296
    public static function moveUpCategory($id)
12297
    {
12298
        $id = (int) $id;
12299
        $em = Database::getManager();
12300
        /** @var CLpCategory $item */
12301
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12302
        if ($item) {
12303
            $position = $item->getPosition() - 1;
12304
            $item->setPosition($position);
12305
            $em->persist($item);
12306
            $em->flush();
12307
        }
12308
    }
12309
12310
    /**
12311
     * @param int $id
12312
     *
12313
     * @throws \Doctrine\ORM\ORMException
12314
     * @throws \Doctrine\ORM\OptimisticLockException
12315
     * @throws \Doctrine\ORM\TransactionRequiredException
12316
     */
12317
    public static function moveDownCategory($id)
12318
    {
12319
        $id = (int) $id;
12320
        $em = Database::getManager();
12321
        /** @var CLpCategory $item */
12322
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12323
        if ($item) {
12324
            $position = $item->getPosition() + 1;
12325
            $item->setPosition($position);
12326
            $em->persist($item);
12327
            $em->flush();
12328
        }
12329
    }
12330
12331
    /**
12332
     * @param int $courseId
12333
     *
12334
     * @throws \Doctrine\ORM\Query\QueryException
12335
     *
12336
     * @return int|mixed
12337
     */
12338
    public static function getCountCategories($courseId)
12339
    {
12340
        if (empty($courseId)) {
12341
            return 0;
12342
        }
12343
        $em = Database::getManager();
12344
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12345
        $query->setParameter('id', $courseId);
12346
12347
        return $query->getSingleScalarResult();
12348
    }
12349
12350
    /**
12351
     * @param int $courseId
12352
     *
12353
     * @return mixed
12354
     */
12355
    public static function getCategories($courseId)
12356
    {
12357
        $em = Database::getManager();
12358
12359
        // Using doctrine extensions
12360
        /** @var SortableRepository $repo */
12361
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12362
        $items = $repo
12363
            ->getBySortableGroupsQuery(['cId' => $courseId])
12364
            ->getResult();
12365
12366
        return $items;
12367
    }
12368
12369
    /**
12370
     * @param int $id
12371
     *
12372
     * @throws \Doctrine\ORM\ORMException
12373
     * @throws \Doctrine\ORM\OptimisticLockException
12374
     * @throws \Doctrine\ORM\TransactionRequiredException
12375
     *
12376
     * @return CLpCategory
12377
     */
12378
    public static function getCategory($id)
12379
    {
12380
        $id = (int) $id;
12381
        $em = Database::getManager();
12382
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12383
12384
        return $item;
12385
    }
12386
12387
    /**
12388
     * @param int $courseId
12389
     *
12390
     * @return array
12391
     */
12392
    public static function getCategoryByCourse($courseId)
12393
    {
12394
        $em = Database::getManager();
12395
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12396
            ['cId' => $courseId]
12397
        );
12398
12399
        return $items;
12400
    }
12401
12402
    /**
12403
     * @param int $id
12404
     *
12405
     * @throws \Doctrine\ORM\ORMException
12406
     * @throws \Doctrine\ORM\OptimisticLockException
12407
     * @throws \Doctrine\ORM\TransactionRequiredException
12408
     *
12409
     * @return mixed
12410
     */
12411
    public static function deleteCategory($id)
12412
    {
12413
        $em = Database::getManager();
12414
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12415
        if ($item) {
12416
            $courseId = $item->getCId();
12417
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12418
            $query->setParameter('id', $courseId);
12419
            $query->setParameter('catId', $item->getId());
12420
            $lps = $query->getResult();
12421
12422
            // Setting category = 0.
12423
            if ($lps) {
12424
                foreach ($lps as $lpItem) {
12425
                    $lpItem->setCategoryId(0);
12426
                }
12427
            }
12428
12429
            // Removing category.
12430
            $em->remove($item);
12431
            $em->flush();
12432
12433
            $courseInfo = api_get_course_info_by_id($courseId);
12434
            $sessionId = api_get_session_id();
12435
12436
            // Delete link tool
12437
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12438
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12439
            // Delete tools
12440
            $sql = "DELETE FROM $tbl_tool
12441
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12442
            Database::query($sql);
12443
12444
            return true;
12445
        }
12446
12447
        return false;
12448
    }
12449
12450
    /**
12451
     * @param int  $courseId
12452
     * @param bool $addSelectOption
12453
     *
12454
     * @return mixed
12455
     */
12456
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12457
    {
12458
        $items = self::getCategoryByCourse($courseId);
12459
        $cats = [];
12460
        if ($addSelectOption) {
12461
            $cats = [get_lang('SelectACategory')];
12462
        }
12463
12464
        if (!empty($items)) {
12465
            foreach ($items as $cat) {
12466
                $cats[$cat->getId()] = $cat->getName();
12467
            }
12468
        }
12469
12470
        return $cats;
12471
    }
12472
12473
    /**
12474
     * @param string $courseCode
12475
     * @param int    $lpId
12476
     * @param int    $user_id
12477
     *
12478
     * @return learnpath
12479
     */
12480
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12481
    {
12482
        $debug = 0;
12483
        $learnPath = null;
12484
        $lpObject = Session::read('lpobject');
12485
        if ($lpObject !== null) {
12486
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12487
            if ($debug) {
12488
                error_log('getLpFromSession: unserialize');
12489
                error_log('------getLpFromSession------');
12490
                error_log('------unserialize------');
12491
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12492
                error_log("api_get_sessionid: ".api_get_session_id());
12493
            }
12494
        }
12495
12496
        if (!is_object($learnPath)) {
12497
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12498
            if ($debug) {
12499
                error_log('------getLpFromSession------');
12500
                error_log('getLpFromSession: create new learnpath');
12501
                error_log("create new LP with $courseCode - $lpId - $user_id");
12502
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12503
                error_log("api_get_sessionid: ".api_get_session_id());
12504
            }
12505
        }
12506
12507
        return $learnPath;
12508
    }
12509
12510
    /**
12511
     * @param int $itemId
12512
     *
12513
     * @return learnpathItem|false
12514
     */
12515
    public function getItem($itemId)
12516
    {
12517
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12518
            return $this->items[$itemId];
12519
        }
12520
12521
        return false;
12522
    }
12523
12524
    /**
12525
     * @return int
12526
     */
12527
    public function getCurrentAttempt()
12528
    {
12529
        $attempt = $this->getItem($this->get_current_item_id());
12530
        if ($attempt) {
12531
            $attemptId = $attempt->get_attempt_id();
12532
12533
            return $attemptId;
12534
        }
12535
12536
        return 0;
12537
    }
12538
12539
    /**
12540
     * @return int
12541
     */
12542
    public function getCategoryId()
12543
    {
12544
        return (int) $this->categoryId;
12545
    }
12546
12547
    /**
12548
     * @param int $categoryId
12549
     *
12550
     * @return bool
12551
     */
12552
    public function setCategoryId($categoryId)
12553
    {
12554
        $this->categoryId = (int) $categoryId;
12555
        $table = Database::get_course_table(TABLE_LP_MAIN);
12556
        $lp_id = $this->get_id();
12557
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12558
                WHERE iid = $lp_id";
12559
        Database::query($sql);
12560
12561
        return true;
12562
    }
12563
12564
    /**
12565
     * Get whether this is a learning path with the possibility to subscribe
12566
     * users or not.
12567
     *
12568
     * @return int
12569
     */
12570
    public function getSubscribeUsers()
12571
    {
12572
        return $this->subscribeUsers;
12573
    }
12574
12575
    /**
12576
     * Set whether this is a learning path with the possibility to subscribe
12577
     * users or not.
12578
     *
12579
     * @param int $value (0 = false, 1 = true)
12580
     *
12581
     * @return bool
12582
     */
12583
    public function setSubscribeUsers($value)
12584
    {
12585
        $this->subscribeUsers = (int) $value;
12586
        $table = Database::get_course_table(TABLE_LP_MAIN);
12587
        $lp_id = $this->get_id();
12588
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12589
                WHERE iid = $lp_id";
12590
        Database::query($sql);
12591
12592
        return true;
12593
    }
12594
12595
    /**
12596
     * Calculate the count of stars for a user in this LP
12597
     * This calculation is based on the following rules:
12598
     * - the student gets one star when he gets to 50% of the learning path
12599
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12600
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12601
     * - the student gets the final star when the score for the *last* test is >= 80%.
12602
     *
12603
     * @param int $sessionId Optional. The session ID
12604
     *
12605
     * @return int The count of stars
12606
     */
12607
    public function getCalculateStars($sessionId = 0)
12608
    {
12609
        $stars = 0;
12610
        $progress = self::getProgress(
12611
            $this->lp_id,
12612
            $this->user_id,
12613
            $this->course_int_id,
12614
            $sessionId
12615
        );
12616
12617
        if ($progress >= 50) {
12618
            $stars++;
12619
        }
12620
12621
        // Calculate stars chapters evaluation
12622
        $exercisesItems = $this->getExercisesItems();
12623
12624
        if (!empty($exercisesItems)) {
12625
            $totalResult = 0;
12626
12627
            foreach ($exercisesItems as $exerciseItem) {
12628
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12629
                    $this->user_id,
12630
                    $exerciseItem->path,
12631
                    $this->course_int_id,
12632
                    $sessionId,
12633
                    $this->lp_id,
12634
                    $exerciseItem->db_id
12635
                );
12636
12637
                $exerciseResultInfo = end($exerciseResultInfo);
12638
12639
                if (!$exerciseResultInfo) {
12640
                    continue;
12641
                }
12642
12643
                if (!empty($exerciseResultInfo['max_score'])) {
12644
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12645
                } else {
12646
                    $exerciseResult = 0;
12647
                }
12648
                $totalResult += $exerciseResult;
12649
            }
12650
12651
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12652
12653
            if ($totalExerciseAverage >= 50) {
12654
                $stars++;
12655
            }
12656
12657
            if ($totalExerciseAverage >= 80) {
12658
                $stars++;
12659
            }
12660
        }
12661
12662
        // Calculate star for final evaluation
12663
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12664
12665
        if (!empty($finalEvaluationItem)) {
12666
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12667
                $this->user_id,
12668
                $finalEvaluationItem->path,
12669
                $this->course_int_id,
12670
                $sessionId,
12671
                $this->lp_id,
12672
                $finalEvaluationItem->db_id
12673
            );
12674
12675
            $evaluationResultInfo = end($evaluationResultInfo);
12676
12677
            if ($evaluationResultInfo) {
12678
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12679
12680
                if ($evaluationResult >= 80) {
12681
                    $stars++;
12682
                }
12683
            }
12684
        }
12685
12686
        return $stars;
12687
    }
12688
12689
    /**
12690
     * Get the items of exercise type.
12691
     *
12692
     * @return array The items. Otherwise return false
12693
     */
12694
    public function getExercisesItems()
12695
    {
12696
        $exercises = [];
12697
        foreach ($this->items as $item) {
12698
            if ($item->type != 'quiz') {
12699
                continue;
12700
            }
12701
            $exercises[] = $item;
12702
        }
12703
12704
        array_pop($exercises);
12705
12706
        return $exercises;
12707
    }
12708
12709
    /**
12710
     * Get the item of exercise type (evaluation type).
12711
     *
12712
     * @return array The final evaluation. Otherwise return false
12713
     */
12714
    public function getFinalEvaluationItem()
12715
    {
12716
        $exercises = [];
12717
        foreach ($this->items as $item) {
12718
            if ($item->type != 'quiz') {
12719
                continue;
12720
            }
12721
12722
            $exercises[] = $item;
12723
        }
12724
12725
        return array_pop($exercises);
12726
    }
12727
12728
    /**
12729
     * Calculate the total points achieved for the current user in this learning path.
12730
     *
12731
     * @param int $sessionId Optional. The session Id
12732
     *
12733
     * @return int
12734
     */
12735
    public function getCalculateScore($sessionId = 0)
12736
    {
12737
        // Calculate stars chapters evaluation
12738
        $exercisesItems = $this->getExercisesItems();
12739
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12740
        $totalExercisesResult = 0;
12741
        $totalEvaluationResult = 0;
12742
12743
        if ($exercisesItems !== false) {
12744
            foreach ($exercisesItems as $exerciseItem) {
12745
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12746
                    $this->user_id,
12747
                    $exerciseItem->path,
12748
                    $this->course_int_id,
12749
                    $sessionId,
12750
                    $this->lp_id,
12751
                    $exerciseItem->db_id
12752
                );
12753
12754
                $exerciseResultInfo = end($exerciseResultInfo);
12755
12756
                if (!$exerciseResultInfo) {
12757
                    continue;
12758
                }
12759
12760
                $totalExercisesResult += $exerciseResultInfo['score'];
12761
            }
12762
        }
12763
12764
        if (!empty($finalEvaluationItem)) {
12765
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12766
                $this->user_id,
12767
                $finalEvaluationItem->path,
12768
                $this->course_int_id,
12769
                $sessionId,
12770
                $this->lp_id,
12771
                $finalEvaluationItem->db_id
12772
            );
12773
12774
            $evaluationResultInfo = end($evaluationResultInfo);
12775
12776
            if ($evaluationResultInfo) {
12777
                $totalEvaluationResult += $evaluationResultInfo['score'];
12778
            }
12779
        }
12780
12781
        return $totalExercisesResult + $totalEvaluationResult;
12782
    }
12783
12784
    /**
12785
     * Check if URL is not allowed to be show in a iframe.
12786
     *
12787
     * @param string $src
12788
     *
12789
     * @return string
12790
     */
12791
    public function fixBlockedLinks($src)
12792
    {
12793
        $urlInfo = parse_url($src);
12794
12795
        $platformProtocol = 'https';
12796
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12797
            $platformProtocol = 'http';
12798
        }
12799
12800
        $protocolFixApplied = false;
12801
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12802
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12803
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12804
12805
        if ($platformProtocol != $scheme) {
12806
            Session::write('x_frame_source', $src);
12807
            $src = 'blank.php?error=x_frames_options';
12808
            $protocolFixApplied = true;
12809
        }
12810
12811
        if ($protocolFixApplied == false) {
12812
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12813
                // Check X-Frame-Options
12814
                $ch = curl_init();
12815
                $options = [
12816
                    CURLOPT_URL => $src,
12817
                    CURLOPT_RETURNTRANSFER => true,
12818
                    CURLOPT_HEADER => true,
12819
                    CURLOPT_FOLLOWLOCATION => true,
12820
                    CURLOPT_ENCODING => "",
12821
                    CURLOPT_AUTOREFERER => true,
12822
                    CURLOPT_CONNECTTIMEOUT => 120,
12823
                    CURLOPT_TIMEOUT => 120,
12824
                    CURLOPT_MAXREDIRS => 10,
12825
                ];
12826
12827
                $proxySettings = api_get_configuration_value('proxy_settings');
12828
                if (!empty($proxySettings) &&
12829
                    isset($proxySettings['curl_setopt_array'])
12830
                ) {
12831
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12832
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12833
                }
12834
12835
                curl_setopt_array($ch, $options);
12836
                $response = curl_exec($ch);
12837
                $httpCode = curl_getinfo($ch);
12838
                $headers = substr($response, 0, $httpCode['header_size']);
12839
12840
                $error = false;
12841
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12842
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12843
                ) {
12844
                    $error = true;
12845
                }
12846
12847
                if ($error) {
12848
                    Session::write('x_frame_source', $src);
12849
                    $src = 'blank.php?error=x_frames_options';
12850
                }
12851
            }
12852
        }
12853
12854
        return $src;
12855
    }
12856
12857
    /**
12858
     * Check if this LP has a created forum in the basis course.
12859
     *
12860
     * @return bool
12861
     */
12862
    public function lpHasForum()
12863
    {
12864
        $forumTable = Database::get_course_table(TABLE_FORUM);
12865
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12866
12867
        $fakeFrom = "
12868
            $forumTable f
12869
            INNER JOIN $itemProperty ip
12870
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12871
        ";
12872
12873
        $resultData = Database::select(
12874
            'COUNT(f.iid) AS qty',
12875
            $fakeFrom,
12876
            [
12877
                'where' => [
12878
                    'ip.visibility != ? AND ' => 2,
12879
                    'ip.tool = ? AND ' => TOOL_FORUM,
12880
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12881
                    'f.lp_id = ?' => intval($this->lp_id),
12882
                ],
12883
            ],
12884
            'first'
12885
        );
12886
12887
        return $resultData['qty'] > 0;
12888
    }
12889
12890
    /**
12891
     * Get the forum for this learning path.
12892
     *
12893
     * @param int $sessionId
12894
     *
12895
     * @return bool
12896
     */
12897
    public function getForum($sessionId = 0)
12898
    {
12899
        $forumTable = Database::get_course_table(TABLE_FORUM);
12900
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12901
12902
        $fakeFrom = "$forumTable f
12903
            INNER JOIN $itemProperty ip ";
12904
12905
        if ($this->lp_session_id == 0) {
12906
            $fakeFrom .= "
12907
                ON (
12908
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12909
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12910
                    )
12911
                )
12912
            ";
12913
        } else {
12914
            $fakeFrom .= "
12915
                ON (
12916
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12917
                )
12918
            ";
12919
        }
12920
12921
        $resultData = Database::select(
12922
            'f.*',
12923
            $fakeFrom,
12924
            [
12925
                'where' => [
12926
                    'ip.visibility != ? AND ' => 2,
12927
                    'ip.tool = ? AND ' => TOOL_FORUM,
12928
                    'f.session_id = ? AND ' => $sessionId,
12929
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12930
                    'f.lp_id = ?' => intval($this->lp_id),
12931
                ],
12932
            ],
12933
            'first'
12934
        );
12935
12936
        if (empty($resultData)) {
12937
            return false;
12938
        }
12939
12940
        return $resultData;
12941
    }
12942
12943
    /**
12944
     * Create a forum for this learning path.
12945
     *
12946
     * @param int $forumCategoryId
12947
     *
12948
     * @return int The forum ID if was created. Otherwise return false
12949
     */
12950
    public function createForum($forumCategoryId)
12951
    {
12952
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12953
12954
        $forumId = store_forum(
12955
            [
12956
                'lp_id' => $this->lp_id,
12957
                'forum_title' => $this->name,
12958
                'forum_comment' => null,
12959
                'forum_category' => (int) $forumCategoryId,
12960
                'students_can_edit_group' => ['students_can_edit' => 0],
12961
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12962
                'default_view_type_group' => ['default_view_type' => 'flat'],
12963
                'group_forum' => 0,
12964
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12965
            ],
12966
            [],
12967
            true
12968
        );
12969
12970
        return $forumId;
12971
    }
12972
12973
    /**
12974
     * Get the LP Final Item form.
12975
     *
12976
     * @throws Exception
12977
     * @throws HTML_QuickForm_Error
12978
     *
12979
     * @return string
12980
     */
12981
    public function getFinalItemForm()
12982
    {
12983
        $finalItem = $this->getFinalItem();
12984
        $title = '';
12985
12986
        if ($finalItem) {
12987
            $title = $finalItem->get_title();
12988
            $buttonText = get_lang('Save');
12989
            $content = $this->getSavedFinalItem();
12990
        } else {
12991
            $buttonText = get_lang('LPCreateDocument');
12992
            $content = $this->getFinalItemTemplate();
12993
        }
12994
12995
        $courseInfo = api_get_course_info();
12996
        $result = $this->generate_lp_folder($courseInfo);
12997
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12998
        $relative_prefix = '../../';
12999
13000
        $editorConfig = [
13001
            'ToolbarSet' => 'LearningPathDocuments',
13002
            'Width' => '100%',
13003
            'Height' => '500',
13004
            'FullPage' => true,
13005
            'CreateDocumentDir' => $relative_prefix,
13006
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13007
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13008
        ];
13009
13010
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13011
            'type' => 'document',
13012
            'lp_id' => $this->lp_id,
13013
        ]);
13014
13015
        $form = new FormValidator('final_item', 'POST', $url);
13016
        $form->addText('title', get_lang('Title'));
13017
        $form->addButtonSave($buttonText);
13018
        $form->addHtml(
13019
            Display::return_message(
13020
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13021
                'normal',
13022
                false
13023
            )
13024
        );
13025
13026
        $renderer = $form->defaultRenderer();
13027
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13028
13029
        $form->addHtmlEditor(
13030
            'content_lp_certificate',
13031
            null,
13032
            true,
13033
            false,
13034
            $editorConfig,
13035
            true
13036
        );
13037
        $form->addHidden('action', 'add_final_item');
13038
        $form->addHidden('path', Session::read('pathItem'));
13039
        $form->addHidden('previous', $this->get_last());
13040
        $form->setDefaults(
13041
            ['title' => $title, 'content_lp_certificate' => $content]
13042
        );
13043
13044
        if ($form->validate()) {
13045
            $values = $form->exportValues();
13046
            $lastItemId = $this->getLastInFirstLevel();
13047
13048
            if (!$finalItem) {
13049
                $documentId = $this->create_document(
13050
                    $this->course_info,
13051
                    $values['content_lp_certificate'],
13052
                    $values['title']
13053
                );
13054
                $this->add_item(
13055
                    0,
13056
                    $lastItemId,
13057
                    'final_item',
13058
                    $documentId,
13059
                    $values['title'],
13060
                    ''
13061
                );
13062
13063
                Display::addFlash(
13064
                    Display::return_message(get_lang('Added'))
13065
                );
13066
            } else {
13067
                $this->edit_document($this->course_info);
13068
            }
13069
        }
13070
13071
        return $form->returnForm();
13072
    }
13073
13074
    /**
13075
     * Check if the current lp item is first, both, last or none from lp list.
13076
     *
13077
     * @param int $currentItemId
13078
     *
13079
     * @return string
13080
     */
13081
    public function isFirstOrLastItem($currentItemId)
13082
    {
13083
        $lpItemId = [];
13084
        $typeListNotToVerify = self::getChapterTypes();
13085
13086
        // Using get_toc() function instead $this->items because returns the correct order of the items
13087
        foreach ($this->get_toc() as $item) {
13088
            if (!in_array($item['type'], $typeListNotToVerify)) {
13089
                $lpItemId[] = $item['id'];
13090
            }
13091
        }
13092
13093
        $lastLpItemIndex = count($lpItemId) - 1;
13094
        $position = array_search($currentItemId, $lpItemId);
13095
13096
        switch ($position) {
13097
            case 0:
13098
                if (!$lastLpItemIndex) {
13099
                    $answer = 'both';
13100
                    break;
13101
                }
13102
13103
                $answer = 'first';
13104
                break;
13105
            case $lastLpItemIndex:
13106
                $answer = 'last';
13107
                break;
13108
            default:
13109
                $answer = 'none';
13110
        }
13111
13112
        return $answer;
13113
    }
13114
13115
    /**
13116
     * Get whether this is a learning path with the accumulated SCORM time or not.
13117
     *
13118
     * @return int
13119
     */
13120
    public function getAccumulateScormTime()
13121
    {
13122
        return $this->accumulateScormTime;
13123
    }
13124
13125
    /**
13126
     * Set whether this is a learning path with the accumulated SCORM time or not.
13127
     *
13128
     * @param int $value (0 = false, 1 = true)
13129
     *
13130
     * @return bool Always returns true
13131
     */
13132
    public function setAccumulateScormTime($value)
13133
    {
13134
        $this->accumulateScormTime = (int) $value;
13135
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13136
        $lp_id = $this->get_id();
13137
        $sql = "UPDATE $lp_table 
13138
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13139
                WHERE iid = $lp_id";
13140
        Database::query($sql);
13141
13142
        return true;
13143
    }
13144
13145
    /**
13146
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13147
     * the new learning path tool.
13148
     *
13149
     * The function is a big switch on tool type.
13150
     * In each case, we query the corresponding table for information and build the link
13151
     * with that information.
13152
     *
13153
     * @author Yannick Warnier <[email protected]> - rebranding based on
13154
     * previous work (display_addedresource_link_in_learnpath())
13155
     *
13156
     * @param int $course_id      Course code
13157
     * @param int $learningPathId The learning path ID (in lp table)
13158
     * @param int $id_in_path     the unique index in the items table
13159
     * @param int $lpViewId
13160
     *
13161
     * @return string
13162
     */
13163
    public static function rl_get_resource_link_for_learnpath(
13164
        $course_id,
13165
        $learningPathId,
13166
        $id_in_path,
13167
        $lpViewId
13168
    ) {
13169
        $session_id = api_get_session_id();
13170
        $course_info = api_get_course_info_by_id($course_id);
13171
13172
        $learningPathId = (int) $learningPathId;
13173
        $id_in_path = (int) $id_in_path;
13174
        $lpViewId = (int) $lpViewId;
13175
13176
        $em = Database::getManager();
13177
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13178
13179
        /** @var CLpItem $rowItem */
13180
        $rowItem = $lpItemRepo->findOneBy([
13181
            'cId' => $course_id,
13182
            'lpId' => $learningPathId,
13183
            'iid' => $id_in_path,
13184
        ]);
13185
13186
        if (!$rowItem) {
13187
            // Try one more time with "id"
13188
            /** @var CLpItem $rowItem */
13189
            $rowItem = $lpItemRepo->findOneBy([
13190
                'cId' => $course_id,
13191
                'lpId' => $learningPathId,
13192
                'id' => $id_in_path,
13193
            ]);
13194
13195
            if (!$rowItem) {
13196
                return -1;
13197
            }
13198
        }
13199
13200
        $course_code = $course_info['code'];
13201
        $type = $rowItem->getItemType();
13202
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13203
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13204
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13205
        $link = '';
13206
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13207
13208
        switch ($type) {
13209
            case 'dir':
13210
                return $main_dir_path.'lp/blank.php';
13211
            case TOOL_CALENDAR_EVENT:
13212
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13213
            case TOOL_ANNOUNCEMENT:
13214
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13215
            case TOOL_LINK:
13216
                $linkInfo = Link::getLinkInfo($id);
13217
                if (isset($linkInfo['url'])) {
13218
                    return $linkInfo['url'];
13219
                }
13220
13221
                return '';
13222
            case TOOL_QUIZ:
13223
                if (empty($id)) {
13224
                    return '';
13225
                }
13226
13227
                // Get the lp_item_view with the highest view_count.
13228
                $learnpathItemViewResult = $em
13229
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13230
                    ->findBy(
13231
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13232
                        ['viewCount' => 'DESC'],
13233
                        1
13234
                    );
13235
                /** @var CLpItemView $learnpathItemViewData */
13236
                $learnpathItemViewData = current($learnpathItemViewResult);
13237
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13238
13239
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13240
                    .http_build_query([
13241
                        'lp_init' => 1,
13242
                        'learnpath_item_view_id' => $learnpathItemViewId,
13243
                        'learnpath_id' => $learningPathId,
13244
                        'learnpath_item_id' => $id_in_path,
13245
                        'exerciseId' => $id,
13246
                    ]);
13247
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13248
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13249
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13250
                $myrow = Database::fetch_array($result);
13251
                $path = $myrow['path'];
13252
13253
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13254
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13255
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13256
            case TOOL_FORUM:
13257
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13258
            case TOOL_THREAD:
13259
                // forum post
13260
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13261
                if (empty($id)) {
13262
                    return '';
13263
                }
13264
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13265
                $result = Database::query($sql);
13266
                $myrow = Database::fetch_array($result);
13267
13268
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13269
                    .$extraParams;
13270
            case TOOL_POST:
13271
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13272
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13273
                $myrow = Database::fetch_array($result);
13274
13275
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13276
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13277
            case TOOL_READOUT_TEXT:
13278
                return api_get_path(WEB_CODE_PATH).
13279
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13280
            case TOOL_DOCUMENT:
13281
                $document = $em
13282
                    ->getRepository('ChamiloCourseBundle:CDocument')
13283
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
13284
13285
                if (empty($document)) {
13286
                    // Try with normal id
13287
                    $document = $em
13288
                        ->getRepository('ChamiloCourseBundle:CDocument')
13289
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
13290
13291
                    if (empty($document)) {
13292
                        return '';
13293
                    }
13294
                }
13295
13296
                $documentPathInfo = pathinfo($document->getPath());
13297
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
13298
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13299
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13300
13301
                $openmethod = 2;
13302
                $officedoc = false;
13303
                Session::write('openmethod', $openmethod);
13304
                Session::write('officedoc', $officedoc);
13305
13306
                if ($showDirectUrl) {
13307
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13308
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13309
                        if (Link::isPdfLink($file)) {
13310
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13311
13312
                            return $pdfUrl;
13313
                        }
13314
                    }
13315
13316
                    return $file;
13317
                }
13318
13319
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13320
            case TOOL_LP_FINAL_ITEM:
13321
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13322
                    .$extraParams;
13323
            case 'assignments':
13324
                return $main_dir_path.'work/work.php?'.$extraParams;
13325
            case TOOL_DROPBOX:
13326
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13327
            case 'introduction_text': //DEPRECATED
13328
                return '';
13329
            case TOOL_COURSE_DESCRIPTION:
13330
                return $main_dir_path.'course_description?'.$extraParams;
13331
            case TOOL_GROUP:
13332
                return $main_dir_path.'group/group.php?'.$extraParams;
13333
            case TOOL_USER:
13334
                return $main_dir_path.'user/user.php?'.$extraParams;
13335
            case TOOL_STUDENTPUBLICATION:
13336
                if (!empty($rowItem->getPath())) {
13337
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13338
                }
13339
13340
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13341
        }
13342
13343
        return $link;
13344
    }
13345
13346
    /**
13347
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13348
     *
13349
     * @author Yannick Warnier <[email protected]>
13350
     *
13351
     * @param string $course_code    Course code
13352
     * @param int    $learningPathId
13353
     * @param int    $id_in_path     The resource ID
13354
     *
13355
     * @return string
13356
     */
13357
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13358
    {
13359
        $_course = api_get_course_info($course_code);
13360
        if (empty($_course)) {
13361
            return '';
13362
        }
13363
        $course_id = $_course['real_id'];
13364
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13365
        $learningPathId = (int) $learningPathId;
13366
        $id_in_path = (int) $id_in_path;
13367
13368
        $sql = "SELECT item_type, title, ref 
13369
                FROM $tbl_lp_item
13370
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13371
        $res_item = Database::query($sql);
13372
13373
        if (Database::num_rows($res_item) < 1) {
13374
            return '';
13375
        }
13376
        $row_item = Database::fetch_array($res_item);
13377
        $type = strtolower($row_item['item_type']);
13378
        $id = $row_item['ref'];
13379
        $output = '';
13380
13381
        switch ($type) {
13382
            case TOOL_CALENDAR_EVENT:
13383
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13384
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13385
                $myrow = Database::fetch_array($result);
13386
                $output = $myrow['title'];
13387
                break;
13388
            case TOOL_ANNOUNCEMENT:
13389
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13390
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13391
                $myrow = Database::fetch_array($result);
13392
                $output = $myrow['title'];
13393
                break;
13394
            case TOOL_LINK:
13395
                // Doesn't take $target into account.
13396
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13397
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13398
                $myrow = Database::fetch_array($result);
13399
                $output = $myrow['title'];
13400
                break;
13401
            case TOOL_QUIZ:
13402
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13403
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13404
                $myrow = Database::fetch_array($result);
13405
                $output = $myrow['title'];
13406
                break;
13407
            case TOOL_FORUM:
13408
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13409
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13410
                $myrow = Database::fetch_array($result);
13411
                $output = $myrow['forum_name'];
13412
                break;
13413
            case TOOL_THREAD:
13414
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13415
                // Grabbing the title of the post.
13416
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13417
                $result_title = Database::query($sql_title);
13418
                $myrow_title = Database::fetch_array($result_title);
13419
                $output = $myrow_title['post_title'];
13420
                break;
13421
            case TOOL_POST:
13422
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13423
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13424
                $result = Database::query($sql);
13425
                $post = Database::fetch_array($result);
13426
                $output = $post['post_title'];
13427
                break;
13428
            case 'dir':
13429
            case TOOL_DOCUMENT:
13430
                $title = $row_item['title'];
13431
                $output = '-';
13432
                if (!empty($title)) {
13433
                    $output = $title;
13434
                }
13435
                break;
13436
            case 'hotpotatoes':
13437
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13438
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13439
                $myrow = Database::fetch_array($result);
13440
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13441
                $last = count($pathname) - 1; // Making a correct name for the link.
13442
                $filename = $pathname[$last]; // Making a correct name for the link.
13443
                $myrow['path'] = rawurlencode($myrow['path']);
13444
                $output = $filename;
13445
                break;
13446
        }
13447
13448
        return stripslashes($output);
13449
    }
13450
13451
    /**
13452
     * Get the parent names for the current item.
13453
     *
13454
     * @param int $newItemId Optional. The item ID
13455
     *
13456
     * @return array
13457
     */
13458
    public function getCurrentItemParentNames($newItemId = 0)
13459
    {
13460
        $newItemId = $newItemId ?: $this->get_current_item_id();
13461
        $return = [];
13462
        $item = $this->getItem($newItemId);
13463
        $parent = $this->getItem($item->get_parent());
13464
13465
        while ($parent) {
13466
            $return[] = $parent->get_title();
13467
            $parent = $this->getItem($parent->get_parent());
13468
        }
13469
13470
        return array_reverse($return);
13471
    }
13472
13473
    /**
13474
     * Reads and process "lp_subscription_settings" setting.
13475
     *
13476
     * @return array
13477
     */
13478
    public static function getSubscriptionSettings()
13479
    {
13480
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13481
        if (empty($subscriptionSettings)) {
13482
            // By default allow both settings
13483
            $subscriptionSettings = [
13484
                'allow_add_users_to_lp' => true,
13485
                'allow_add_users_to_lp_category' => true,
13486
            ];
13487
        } else {
13488
            $subscriptionSettings = $subscriptionSettings['options'];
13489
        }
13490
13491
        return $subscriptionSettings;
13492
    }
13493
13494
    /**
13495
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13496
     */
13497
    public function exportToCourseBuildFormat()
13498
    {
13499
        if (!api_is_allowed_to_edit()) {
13500
            return false;
13501
        }
13502
13503
        $courseBuilder = new CourseBuilder();
13504
        $itemList = [];
13505
        /** @var learnpathItem $item */
13506
        foreach ($this->items as $item) {
13507
            $itemList[$item->get_type()][] = $item->get_path();
13508
        }
13509
13510
        if (empty($itemList)) {
13511
            return false;
13512
        }
13513
13514
        if (isset($itemList['document'])) {
13515
            // Get parents
13516
            foreach ($itemList['document'] as $documentId) {
13517
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13518
                if (!empty($documentInfo['parents'])) {
13519
                    foreach ($documentInfo['parents'] as $parentInfo) {
13520
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13521
                            continue;
13522
                        }
13523
                        $itemList['document'][] = $parentInfo['iid'];
13524
                    }
13525
                }
13526
            }
13527
13528
            $courseInfo = api_get_course_info();
13529
            foreach ($itemList['document'] as $documentId) {
13530
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13531
                $items = DocumentManager::get_resources_from_source_html(
13532
                    $documentInfo['absolute_path'],
13533
                    true,
13534
                    TOOL_DOCUMENT
13535
                );
13536
13537
                if (!empty($items)) {
13538
                    foreach ($items as $item) {
13539
                        // Get information about source url
13540
                        $url = $item[0]; // url
13541
                        $scope = $item[1]; // scope (local, remote)
13542
                        $type = $item[2]; // type (rel, abs, url)
13543
13544
                        $origParseUrl = parse_url($url);
13545
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13546
13547
                        if ($scope == 'local') {
13548
                            if ($type == 'abs' || $type == 'rel') {
13549
                                $documentFile = strstr($realOrigPath, 'document');
13550
                                if (strpos($realOrigPath, $documentFile) !== false) {
13551
                                    $documentFile = str_replace('document', '', $documentFile);
13552
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13553
                                    // Document found! Add it to the list
13554
                                    if ($itemDocumentId) {
13555
                                        $itemList['document'][] = $itemDocumentId;
13556
                                    }
13557
                                }
13558
                            }
13559
                        }
13560
                    }
13561
                }
13562
            }
13563
13564
            $courseBuilder->build_documents(
13565
                api_get_session_id(),
13566
                $this->get_course_int_id(),
13567
                true,
13568
                $itemList['document']
13569
            );
13570
        }
13571
13572
        if (isset($itemList['quiz'])) {
13573
            $courseBuilder->build_quizzes(
13574
                api_get_session_id(),
13575
                $this->get_course_int_id(),
13576
                true,
13577
                $itemList['quiz']
13578
            );
13579
        }
13580
13581
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13582
13583
        /*if (!empty($itemList['thread'])) {
13584
            $postList = [];
13585
            foreach ($itemList['thread'] as $postId) {
13586
                $post = get_post_information($postId);
13587
                if ($post) {
13588
                    if (!isset($itemList['forum'])) {
13589
                        $itemList['forum'] = [];
13590
                    }
13591
                    $itemList['forum'][] = $post['forum_id'];
13592
                    $postList[] = $postId;
13593
                }
13594
            }
13595
13596
            if (!empty($postList)) {
13597
                $courseBuilder->build_forum_posts(
13598
                    $this->get_course_int_id(),
13599
                    null,
13600
                    null,
13601
                    $postList
13602
                );
13603
            }
13604
        }*/
13605
13606
        if (!empty($itemList['thread'])) {
13607
            $threadList = [];
13608
            $em = Database::getManager();
13609
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13610
            foreach ($itemList['thread'] as $threadId) {
13611
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13612
                $thread = $repo->find($threadId);
13613
                if ($thread) {
13614
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13615
                    $threadList[] = $thread->getIid();
13616
                }
13617
            }
13618
13619
            if (!empty($threadList)) {
13620
                $courseBuilder->build_forum_topics(
13621
                    api_get_session_id(),
13622
                    $this->get_course_int_id(),
13623
                    null,
13624
                    $threadList
13625
                );
13626
            }
13627
        }
13628
13629
        $forumCategoryList = [];
13630
        if (isset($itemList['forum'])) {
13631
            foreach ($itemList['forum'] as $forumId) {
13632
                $forumInfo = get_forums($forumId);
13633
                $forumCategoryList[] = $forumInfo['forum_category'];
13634
            }
13635
        }
13636
13637
        if (!empty($forumCategoryList)) {
13638
            $courseBuilder->build_forum_category(
13639
                api_get_session_id(),
13640
                $this->get_course_int_id(),
13641
                true,
13642
                $forumCategoryList
13643
            );
13644
        }
13645
13646
        if (!empty($itemList['forum'])) {
13647
            $courseBuilder->build_forums(
13648
                api_get_session_id(),
13649
                $this->get_course_int_id(),
13650
                true,
13651
                $itemList['forum']
13652
            );
13653
        }
13654
13655
        if (isset($itemList['link'])) {
13656
            $courseBuilder->build_links(
13657
                api_get_session_id(),
13658
                $this->get_course_int_id(),
13659
                true,
13660
                $itemList['link']
13661
            );
13662
        }
13663
13664
        if (!empty($itemList['student_publication'])) {
13665
            $courseBuilder->build_works(
13666
                api_get_session_id(),
13667
                $this->get_course_int_id(),
13668
                true,
13669
                $itemList['student_publication']
13670
            );
13671
        }
13672
13673
        $courseBuilder->build_learnpaths(
13674
            api_get_session_id(),
13675
            $this->get_course_int_id(),
13676
            true,
13677
            [$this->get_id()],
13678
            false
13679
        );
13680
13681
        $courseBuilder->restoreDocumentsFromList();
13682
13683
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13684
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13685
        $result = DocumentManager::file_send_for_download(
13686
            $zipPath,
13687
            true,
13688
            $this->get_name().'.zip'
13689
        );
13690
13691
        if ($result) {
13692
            api_not_allowed();
13693
        }
13694
13695
        return true;
13696
    }
13697
13698
    /**
13699
     * Get whether this is a learning path with the accumulated work time or not.
13700
     *
13701
     * @return int
13702
     */
13703
    public function getAccumulateWorkTime()
13704
    {
13705
        return (int) $this->accumulateWorkTime;
13706
    }
13707
13708
    /**
13709
     * Get whether this is a learning path with the accumulated work time or not.
13710
     *
13711
     * @return int
13712
     */
13713
    public function getAccumulateWorkTimeTotalCourse()
13714
    {
13715
        $table = Database::get_course_table(TABLE_LP_MAIN);
13716
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13717
                FROM $table 
13718
                WHERE c_id = ".$this->course_int_id;
13719
        $result = Database::query($sql);
13720
        $row = Database::fetch_array($result);
13721
13722
        return (int) $row['total'];
13723
    }
13724
13725
    /**
13726
     * Set whether this is a learning path with the accumulated work time or not.
13727
     *
13728
     * @param int $value (0 = false, 1 = true)
13729
     *
13730
     * @return bool
13731
     */
13732
    public function setAccumulateWorkTime($value)
13733
    {
13734
        if (!api_get_configuration_value('lp_minimum_time')) {
13735
            return false;
13736
        }
13737
13738
        $this->accumulateWorkTime = (int) $value;
13739
        $table = Database::get_course_table(TABLE_LP_MAIN);
13740
        $lp_id = $this->get_id();
13741
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13742
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13743
        Database::query($sql);
13744
13745
        return true;
13746
    }
13747
13748
    /**
13749
     * @param int $lpId
13750
     * @param int $courseId
13751
     *
13752
     * @return mixed
13753
     */
13754
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13755
    {
13756
        $lpId = (int) $lpId;
13757
        $courseId = (int) $courseId;
13758
13759
        $table = Database::get_course_table(TABLE_LP_MAIN);
13760
        $sql = "SELECT accumulate_work_time 
13761
                FROM $table 
13762
                WHERE c_id = $courseId AND id = $lpId";
13763
        $result = Database::query($sql);
13764
        $row = Database::fetch_array($result);
13765
13766
        return $row['accumulate_work_time'];
13767
    }
13768
13769
    /**
13770
     * @param int $courseId
13771
     *
13772
     * @return int
13773
     */
13774
    public static function getAccumulateWorkTimeTotal($courseId)
13775
    {
13776
        $table = Database::get_course_table(TABLE_LP_MAIN);
13777
        $courseId = (int) $courseId;
13778
        $sql = "SELECT SUM(accumulate_work_time) AS total
13779
                FROM $table
13780
                WHERE c_id = $courseId";
13781
        $result = Database::query($sql);
13782
        $row = Database::fetch_array($result);
13783
13784
        return (int) $row['total'];
13785
    }
13786
13787
    /**
13788
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13789
     * and put the images in.
13790
     *
13791
     * @return array
13792
     */
13793
    public static function getIconSelect()
13794
    {
13795
        $theme = api_get_visual_theme();
13796
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13797
        $icons = ['' => get_lang('SelectAnOption')];
13798
13799
        if (is_dir($path)) {
13800
            $finder = new Finder();
13801
            $finder->files()->in($path);
13802
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13803
            /** @var SplFileInfo $file */
13804
            foreach ($finder as $file) {
13805
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13806
                    $icons[$file->getFilename()] = $file->getFilename();
13807
                }
13808
            }
13809
        }
13810
13811
        return $icons;
13812
    }
13813
13814
    /**
13815
     * @param int $lpId
13816
     *
13817
     * @return string
13818
     */
13819
    public static function getSelectedIcon($lpId)
13820
    {
13821
        $extraFieldValue = new ExtraFieldValue('lp');
13822
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13823
        $icon = '';
13824
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13825
            $icon = $lpIcon['value'];
13826
        }
13827
13828
        return $icon;
13829
    }
13830
13831
    /**
13832
     * @param int $lpId
13833
     *
13834
     * @return string
13835
     */
13836
    public static function getSelectedIconHtml($lpId)
13837
    {
13838
        $icon = self::getSelectedIcon($lpId);
13839
13840
        if (empty($icon)) {
13841
            return '';
13842
        }
13843
13844
        $theme = api_get_visual_theme();
13845
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13846
13847
        return Display::img($path);
13848
    }
13849
13850
    /**
13851
     * Get the depth level of LP item.
13852
     *
13853
     * @param array $items
13854
     * @param int   $currentItemId
13855
     *
13856
     * @return int
13857
     */
13858
    private static function get_level_for_item($items, $currentItemId)
13859
    {
13860
        $parentItemId = 0;
13861
        if (isset($items[$currentItemId])) {
13862
            $parentItemId = $items[$currentItemId]->parent;
13863
        }
13864
13865
        if ($parentItemId == 0) {
13866
            return 0;
13867
        } else {
13868
            return self::get_level_for_item($items, $parentItemId) + 1;
13869
        }
13870
    }
13871
13872
    /**
13873
     * Generate the link for a learnpath category as course tool.
13874
     *
13875
     * @param int $categoryId
13876
     *
13877
     * @return string
13878
     */
13879
    private static function getCategoryLinkForTool($categoryId)
13880
    {
13881
        $categoryId = (int) $categoryId;
13882
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13883
            .http_build_query(
13884
                [
13885
                    'action' => 'view_category',
13886
                    'id' => $categoryId,
13887
                ]
13888
            );
13889
13890
        return $link;
13891
    }
13892
13893
    /**
13894
     * Return the scorm item type object with spaces replaced with _
13895
     * The return result is use to build a css classname like scorm_type_$return.
13896
     *
13897
     * @param $in_type
13898
     *
13899
     * @return mixed
13900
     */
13901
    private static function format_scorm_type_item($in_type)
13902
    {
13903
        return str_replace(' ', '_', $in_type);
13904
    }
13905
13906
    /**
13907
     * Check and obtain the lp final item if exist.
13908
     *
13909
     * @return learnpathItem
13910
     */
13911
    private function getFinalItem()
13912
    {
13913
        if (empty($this->items)) {
13914
            return null;
13915
        }
13916
13917
        foreach ($this->items as $item) {
13918
            if ($item->type !== 'final_item') {
13919
                continue;
13920
            }
13921
13922
            return $item;
13923
        }
13924
    }
13925
13926
    /**
13927
     * Get the LP Final Item Template.
13928
     *
13929
     * @return string
13930
     */
13931
    private function getFinalItemTemplate()
13932
    {
13933
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13934
    }
13935
13936
    /**
13937
     * Get the LP Final Item Url.
13938
     *
13939
     * @return string
13940
     */
13941
    private function getSavedFinalItem()
13942
    {
13943
        $finalItem = $this->getFinalItem();
13944
        $doc = DocumentManager::get_document_data_by_id(
13945
            $finalItem->path,
13946
            $this->cc
13947
        );
13948
        if ($doc && file_exists($doc['absolute_path'])) {
13949
            return file_get_contents($doc['absolute_path']);
13950
        }
13951
13952
        return '';
13953
    }
13954
}
13955