Passed
Push — 1.11.x ( c17041...81c710 )
by Julito
12:50
created

learnpath   F

Complexity

Total Complexity 1874

Size/Duplication

Total Lines 13897
Duplicated Lines 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
eloc 7687
dl 0
loc 13897
rs 0.8
c 2
b 1
f 0
wmc 1874

225 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
A getCourseCode() 0 3 1
A getChapterTypes() 0 4 1
F delete() 0 111 18
B get_iv_objectives_array() 0 38 6
F get_link() 0 321 55
B getChildrenToc() 0 49 11
A get_author() 0 7 2
A getHideTableOfContents() 0 3 1
A get_type() 0 8 3
A get_maker() 0 7 2
A get_type_static() 0 16 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A get_preview_image() 0 7 2
A get_teacher_toc_buttons() 0 21 4
B getTOCTree() 0 44 8
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
B get_preview_image_path() 0 28 7
B get_iv_interactions_array() 0 54 8
A getNameNoTags() 0 3 1
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 7
A get_flat_ordered_items_list() 0 35 5
A get_objectives_count_from_db() 0 16 2
C getParentToc() 0 54 13
D getListArrayToc() 0 67 11
A get_lp_session_id() 0 7 2
A get_toc() 0 18 2
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
A next() 0 22 5
A get_update_queue() 0 3 1
D prerequisites_match() 0 69 16
A previous() 0 11 1
B move_down() 0 54 8
A open() 0 9 1
A categoryIsPublished() 0 26 2
C toggle_publish() 0 80 11
A get_user_id() 0 7 2
A toggle_visibility() 0 14 2
A save_current() 0 32 6
B restart() 0 40 6
B move_up() 0 51 8
A has_audio() 0 11 3
B get_view() 0 49 7
D move_item() 0 125 18
A toggleCategoryVisibility() 0 14 2
A get_view_id() 0 7 2
B toggleCategoryPublish() 0 88 9
F categoryIsVisibleForStudent() 0 89 19
B save_item() 0 47 9
B update_default_view_mode() 0 33 6
D display_edit_item() 0 121 21
A set_autolaunch() 0 27 2
F display_thread_form() 0 186 41
B save_last() 0 52 11
A copy() 0 26 1
A switch_attempt_mode() 0 16 4
F create_document() 0 169 30
A tree_array() 0 4 1
A set_jslib() 0 15 2
A update_default_scorm_commit() 0 25 4
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
C start_current_item() 0 40 16
A set_publicated_on() 0 22 3
B set_current_item() 0 33 10
A display_lp_prerequisites_list() 0 31 5
A set_prerequisite() 0 10 1
A set_modified_on() 0 10 1
F display_link_form() 0 169 33
F display_document_form() 0 346 72
A set_preview_image() 0 11 1
F createReadOutText() 0 135 27
F displayFrmReadOutText() 0 272 59
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
F display_item_form() 0 231 39
A set_seriousgame_mode() 0 23 4
A returnLpItemList() 0 15 2
B create_tree_array() 0 38 11
B get_links() 0 109 6
B set_terms_by_prefix() 0 68 10
A set_use_max_score() 0 12 1
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 set_theme() 0 11 1
A set_author() 0 10 1
A set_attempt_mode() 0 32 5
A update_display_order() 0 29 5
B generate_lp_folder() 0 57 8
F display_quiz_form() 0 169 35
A set_proximity() 0 15 2
C display_item() 0 98 16
B overview() 0 50 9
A set_expired_on() 0 23 3
B print_recursive() 0 40 10
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B display_move_item() 0 56 11
F display_hotpotatoes_form() 0 155 36
A set_encoding() 0 19 4
C build_action_menu() 0 145 10
A display_resources() 0 55 2
B verify_document_size() 0 22 8
B get_documents() 0 99 2
C get_forums() 0 111 11
F edit_document() 0 82 17
A delete_lp_image() 0 17 5
F display_forum_form() 0 173 39
A update_scorm_debug() 0 23 4
A update_reinit() 0 23 4
A get_extension() 0 5 1
A generate_learning_path_folder() 0 28 4
A set_hide_toc_frame() 0 15 2
B get_attempt_mode() 0 21 9
A return_new_tree() 0 34 4
A set_maker() 0 14 2
A get_student_publications() 0 41 3
F display_student_publication_form() 0 148 29
A set_name() 0 32 3
F get_exercises() 0 162 13
A select_previous_item_id() 0 22 1
F display_item_prerequisites_form() 0 170 19
B get_js_dropdown_array() 0 78 6
B display_manipulate() 0 97 9
F processBuildMenuElements() 0 441 53
A getProgressBar() 0 5 1
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
F edit_item() 0 236 22
A get_last() 0 10 2
A get_progress_bar() 0 12 1
F add_item() 0 238 16
A get_previous_index() 0 16 5
C isBlockedByPrerequisite() 0 68 13
B get_progress_bar_text() 0 56 11
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
F __construct() 0 320 52
A close() 0 13 2
C get_mediaplayer() 0 87 13
A getProgressFromLpList() 0 32 4
F autocomplete_parents() 0 101 17
B edit_item_prereq() 0 41 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
F is_lp_visible_for_student() 0 140 26
A getProgress() 0 23 2
A get_complete_items_count() 0 24 5
B delete_item() 0 70 6
A getTotalItemsCountWithoutDirs() 0 11 3
F add_lp() 0 145 14
C get_navigation_bar() 0 73 10
B get_next_index() 0 23 7
D getPackageType() 0 90 20
A get_common_index_terms_by_prefix() 0 17 3
A get_previous_item_id() 0 5 1
A delete_children_items() 0 22 4
A createCategory() 0 27 3
A getLpList() 0 9 1
A updateCategory() 0 9 2
A getCountCategories() 0 10 2
A moveDownCategory() 0 11 2
A moveUpCategory() 0 11 2
A getCategories() 0 9 1
A getCategory() 0 6 1
B getCalculateScore() 0 47 6
A getCategoryFromCourseIntoSelect() 0 15 4
A getAccumulateScormTime() 0 3 1
C fixBlockedLinks() 0 64 11
A createForum() 0 21 1
A getCategorySessionId() 0 19 3
A getUserIdentifierForExternalServices() 0 11 3
A getAccumulateWorkTime() 0 3 1
A getForum() 0 44 3
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
A getSelectedIcon() 0 10 3
B sortItemByOrderList() 0 48 7
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A getItemsForForm() 0 39 3
A getItem() 0 7 3
A getCategoryId() 0 3 1
A setSubscribeUsers() 0 10 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 12 3
A cleanItemTitle() 0 5 1
A format_scorm_type_item() 0 3 1
A getCurrentItemParentNames() 0 13 3
A getSubscribeUsers() 0 3 1
A deleteCategory() 0 37 4
A getAccumulateWorkTimeTotalCourse() 0 10 1
B isFirstOrLastItem() 0 32 6
C getCalculateStars() 0 80 12
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
A getCategoryByCourse() 0 5 1
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
A getCurrentAttempt() 0 10 2
A getUseScoreAsProgress() 0 18 5
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 11 1
A getFinalItemTemplate() 0 3 1
F rl_get_resource_link_for_learnpath() 0 178 34
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 30
A get_level_for_item() 0 11 3
A lpHasForum() 0 26 1
A setAccumulateWorkTime() 0 14 2

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
5
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
9
use Chamilo\CourseBundle\Entity\CDocument;
10
use Chamilo\CourseBundle\Entity\CItemProperty;
11
use Chamilo\CourseBundle\Entity\CLp;
12
use Chamilo\CourseBundle\Entity\CLpCategory;
13
use Chamilo\CourseBundle\Entity\CLpItem;
14
use Chamilo\CourseBundle\Entity\CLpItemView;
15
use Chamilo\CourseBundle\Entity\CTool;
16
use Chamilo\UserBundle\Entity\User;
17
use ChamiloSession as Session;
18
use Gedmo\Sortable\Entity\Repository\SortableRepository;
19
use Symfony\Component\Filesystem\Filesystem;
20
use Symfony\Component\Finder\Finder;
21
22
/**
23
 * Class learnpath
24
 * This class defines the parent attributes and methods for Chamilo learnpaths
25
 * and SCORM learnpaths. It is used by the scorm class.
26
 *
27
 * @todo decouple class
28
 *
29
 * @author  Yannick Warnier <[email protected]>
30
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
31
 */
32
class learnpath
33
{
34
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
35
    const STATUS_CSS_CLASS_NAME = [
36
        'not attempted' => 'scorm_not_attempted',
37
        'incomplete' => 'scorm_not_attempted',
38
        'failed' => 'scorm_failed',
39
        'completed' => 'scorm_completed',
40
        'passed' => 'scorm_completed',
41
        'succeeded' => 'scorm_completed',
42
        'browsed' => 'scorm_completed',
43
    ];
44
45
    public $attempt = 0; // The number for the current ID view.
46
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
47
    public $current; // Id of the current item the user is viewing.
48
    public $current_score; // The score of the current item.
49
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
50
    public $current_time_stop; // The time the user closed this resource.
51
    public $default_status = 'not attempted';
52
    public $encoding = 'UTF-8';
53
    public $error = '';
54
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
55
    public $index; // The index of the active learnpath_item in $ordered_items array.
56
    public $items = [];
57
    public $last; // item_id of last item viewed in the learning path.
58
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
59
    public $license; // Which license this course has been given - not used yet on 20060522.
60
    public $lp_id; // DB iid for this learnpath.
61
    public $lp_view_id; // DB ID for lp_view
62
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
63
    public $message = '';
64
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
65
    public $name; // Learnpath name (they generally have one).
66
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
67
    public $path = ''; // Path inside the scorm directory (if scorm).
68
    public $theme; // The current theme of the learning path.
69
    public $preview_image; // The current image of the learning path.
70
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
71
    public $accumulateWorkTime; // The min time of learnpath
72
73
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
74
    public $prevent_reinit = 1;
75
76
    // Describes the mode of progress bar display.
77
    public $seriousgame_mode = 0;
78
    public $progress_bar_mode = '%';
79
80
    // Percentage progress as saved in the db.
81
    public $progress_db = 0;
82
    public $proximity; // Wether the content is distant or local or unknown.
83
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
84
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
85
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
86
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
87
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
88
    public $user_id; //ID of the user that is viewing/using the course
89
    public $update_queue = [];
90
    public $scorm_debug = 0;
91
    public $arrMenu = []; // Array for the menu items.
92
    public $debug = 0; // Logging level.
93
    public $lp_session_id = 0;
94
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
95
    public $prerequisite = 0;
96
    public $use_max_score = 1; // 1 or 0
97
    public $subscribeUsers = 0; // Subscribe users or not
98
    public $created_on = '';
99
    public $modified_on = '';
100
    public $publicated_on = '';
101
    public $expired_on = '';
102
    public $ref = null;
103
    public $course_int_id;
104
    public $course_info = [];
105
    public $categoryId;
106
107
    /**
108
     * Constructor.
109
     * Needs a database handler, a course code and a learnpath id from the database.
110
     * Also builds the list of items into $this->items.
111
     *
112
     * @param string $course  Course code
113
     * @param int    $lp_id   c_lp.iid
114
     * @param int    $user_id
115
     */
116
    public function __construct($course, $lp_id, $user_id)
117
    {
118
        $debug = $this->debug;
119
        $this->encoding = api_get_system_encoding();
120
        if ($debug) {
121
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
122
        }
123
        if (empty($course)) {
124
            $course = api_get_course_id();
125
        }
126
        $course_info = api_get_course_info($course);
127
        if (!empty($course_info)) {
128
            $this->cc = $course_info['code'];
129
            $this->course_info = $course_info;
130
            $course_id = $course_info['real_id'];
131
        } else {
132
            $this->error = 'Course code does not exist in database.';
133
        }
134
135
        $lp_id = (int) $lp_id;
136
        $course_id = (int) $course_id;
137
        $this->set_course_int_id($course_id);
138
        // Check learnpath ID.
139
        if (empty($lp_id) || empty($course_id)) {
140
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
141
        } else {
142
            // TODO: Make it flexible to use any course_code (still using env course code here).
143
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
144
            $sql = "SELECT * FROM $lp_table
145
                    WHERE iid = $lp_id";
146
            if ($debug) {
147
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
148
            }
149
            $res = Database::query($sql);
150
            if (Database::num_rows($res) > 0) {
151
                $this->lp_id = $lp_id;
152
                $row = Database::fetch_array($res);
153
                $this->type = $row['lp_type'];
154
                $this->name = stripslashes($row['name']);
155
                $this->proximity = $row['content_local'];
156
                $this->theme = $row['theme'];
157
                $this->maker = $row['content_maker'];
158
                $this->prevent_reinit = $row['prevent_reinit'];
159
                $this->seriousgame_mode = $row['seriousgame_mode'];
160
                $this->license = $row['content_license'];
161
                $this->scorm_debug = $row['debug'];
162
                $this->js_lib = $row['js_lib'];
163
                $this->path = $row['path'];
164
                $this->preview_image = $row['preview_image'];
165
                $this->author = $row['author'];
166
                $this->hide_toc_frame = $row['hide_toc_frame'];
167
                $this->lp_session_id = $row['session_id'];
168
                $this->use_max_score = $row['use_max_score'];
169
                $this->subscribeUsers = $row['subscribe_users'];
170
                $this->created_on = $row['created_on'];
171
                $this->modified_on = $row['modified_on'];
172
                $this->ref = $row['ref'];
173
                $this->categoryId = $row['category_id'];
174
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
175
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
176
177
                if (!empty($row['publicated_on'])) {
178
                    $this->publicated_on = $row['publicated_on'];
179
                }
180
181
                if (!empty($row['expired_on'])) {
182
                    $this->expired_on = $row['expired_on'];
183
                }
184
                if ($this->type == 2) {
185
                    if ($row['force_commit'] == 1) {
186
                        $this->force_commit = true;
187
                    }
188
                }
189
                $this->mode = $row['default_view_mod'];
190
191
                // Check user ID.
192
                if (empty($user_id)) {
193
                    $this->error = 'User ID is empty';
194
                } else {
195
                    $userInfo = api_get_user_info($user_id);
196
                    if (!empty($userInfo)) {
197
                        $this->user_id = $userInfo['user_id'];
198
                    } else {
199
                        $this->error = 'User ID does not exist in database #'.$user_id;
200
                    }
201
                }
202
203
                // End of variables checking.
204
                $session_id = api_get_session_id();
205
                //  Get the session condition for learning paths of the base + session.
206
                $session = api_get_session_condition($session_id);
207
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
208
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
209
210
                // Selecting by view_count descending allows to get the highest view_count first.
211
                $sql = "SELECT * FROM $lp_table
212
                        WHERE
213
                            c_id = $course_id AND
214
                            lp_id = $lp_id AND
215
                            user_id = $user_id
216
                            $session
217
                        ORDER BY view_count DESC";
218
                $res = Database::query($sql);
219
                if ($debug) {
220
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
221
                }
222
223
                if (Database::num_rows($res) > 0) {
224
                    if ($debug) {
225
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
226
                    }
227
                    $row = Database::fetch_array($res);
228
                    $this->attempt = $row['view_count'];
229
                    $this->lp_view_id = $row['id'];
230
                    $this->last_item_seen = $row['last_item'];
231
                    $this->progress_db = $row['progress'];
232
                    $this->lp_view_session_id = $row['session_id'];
233
                } elseif (!api_is_invitee()) {
234
                    if ($debug) {
235
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
236
                    }
237
                    $this->attempt = 1;
238
                    $params = [
239
                        'c_id' => $course_id,
240
                        'lp_id' => $lp_id,
241
                        'user_id' => $user_id,
242
                        'view_count' => 1,
243
                        'session_id' => $session_id,
244
                        'last_item' => 0,
245
                    ];
246
                    $this->last_item_seen = 0;
247
                    $this->lp_view_session_id = $session_id;
248
                    $this->lp_view_id = Database::insert($lp_table, $params);
249
                    if (!empty($this->lp_view_id)) {
250
                        $sql = "UPDATE $lp_table SET id = iid
251
                                WHERE iid = ".$this->lp_view_id;
252
                        Database::query($sql);
253
                    }
254
                }
255
256
                // Initialise items.
257
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
258
                $sql = "SELECT * FROM $lp_item_table
259
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
260
                        ORDER BY parent_item_id, display_order";
261
                $res = Database::query($sql);
262
263
                $lp_item_id_list = [];
264
                while ($row = Database::fetch_array($res)) {
265
                    $lp_item_id_list[] = $row['iid'];
266
                    switch ($this->type) {
267
                        case 3: //aicc
268
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
269
                            if (is_object($oItem)) {
270
                                $my_item_id = $oItem->get_id();
271
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
272
                                $oItem->set_prevent_reinit($this->prevent_reinit);
273
                                // Don't use reference here as the next loop will make the pointed object change.
274
                                $this->items[$my_item_id] = $oItem;
275
                                $this->refs_list[$oItem->ref] = $my_item_id;
276
                                if ($debug) {
277
                                    error_log(
278
                                        'learnpath::__construct() - '.
279
                                        'aicc object with id '.$my_item_id.
280
                                        ' set in items[]',
281
                                        0
282
                                    );
283
                                }
284
                            }
285
                            break;
286
                        case 2:
287
                            $oItem = new scormItem('db', $row['iid'], $course_id);
288
                            if (is_object($oItem)) {
289
                                $my_item_id = $oItem->get_id();
290
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
291
                                $oItem->set_prevent_reinit($this->prevent_reinit);
292
                                // Don't use reference here as the next loop will make the pointed object change.
293
                                $this->items[$my_item_id] = $oItem;
294
                                $this->refs_list[$oItem->ref] = $my_item_id;
295
                                if ($debug) {
296
                                    error_log('object with id '.$my_item_id.' set in items[]');
297
                                }
298
                            }
299
                            break;
300
                        case 1:
301
                        default:
302
                            if ($debug) {
303
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
304
                            }
305
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
306
307
                            if ($debug) {
308
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
309
                            }
310
                            if (is_object($oItem)) {
311
                                $my_item_id = $oItem->get_id();
312
                                // Moved down to when we are sure the item_view exists.
313
                                //$oItem->set_lp_view($this->lp_view_id);
314
                                $oItem->set_prevent_reinit($this->prevent_reinit);
315
                                // Don't use reference here as the next loop will make the pointed object change.
316
                                $this->items[$my_item_id] = $oItem;
317
                                $this->refs_list[$my_item_id] = $my_item_id;
318
                                if ($debug) {
319
                                    error_log(
320
                                        'learnpath::__construct() '.__LINE__.
321
                                        ' - object with id '.$my_item_id.' set in items[]'
322
                                    );
323
                                }
324
                            }
325
                            break;
326
                    }
327
328
                    // Setting the object level with variable $this->items[$i][parent]
329
                    foreach ($this->items as $itemLPObject) {
330
                        $level = self::get_level_for_item(
331
                            $this->items,
332
                            $itemLPObject->db_id
333
                        );
334
                        $itemLPObject->level = $level;
335
                    }
336
337
                    // Setting the view in the item object.
338
                    if (is_object($this->items[$row['iid']])) {
339
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
340
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
341
                            $this->items[$row['iid']]->current_start_time = 0;
342
                            $this->items[$row['iid']]->current_stop_time = 0;
343
                        }
344
                    }
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
                        $status_list = [];
361
                        $res = Database::query($sql);
362
                        while ($row = Database:: fetch_array($res)) {
363
                            $status_list[$row['lp_item_id']] = $row['status'];
364
                        }
365
366
                        foreach ($lp_item_id_list as $item_id) {
367
                            if (isset($status_list[$item_id])) {
368
                                $status = $status_list[$item_id];
369
                                if (is_object($this->items[$item_id])) {
370
                                    $this->items[$item_id]->set_status($status);
371
                                    if (empty($status)) {
372
                                        $this->items[$item_id]->set_status(
373
                                            $this->default_status
374
                                        );
375
                                    }
376
                                }
377
                            } else {
378
                                if (!api_is_invitee()) {
379
                                    if (is_object($this->items[$item_id])) {
380
                                        $this->items[$item_id]->set_status(
381
                                            $this->default_status
382
                                        );
383
                                    }
384
385
                                    if (!empty($this->lp_view_id)) {
386
                                        // Add that row to the lp_item_view table so that
387
                                        // we have something to show in the stats page.
388
                                        $params = [
389
                                            'c_id' => $course_id,
390
                                            'lp_item_id' => $item_id,
391
                                            'lp_view_id' => $this->lp_view_id,
392
                                            'view_count' => 1,
393
                                            'status' => 'not attempted',
394
                                            'start_time' => time(),
395
                                            'total_time' => 0,
396
                                            'score' => 0,
397
                                        ];
398
                                        $insertId = Database::insert($itemViewTable, $params);
399
400
                                        if ($insertId) {
401
                                            $sql = "UPDATE $itemViewTable SET id = iid
402
                                                    WHERE iid = $insertId";
403
                                            Database::query($sql);
404
                                        }
405
406
                                        $this->items[$item_id]->set_lp_view(
407
                                            $this->lp_view_id,
408
                                            $course_id
409
                                        );
410
                                    }
411
                                }
412
                            }
413
                        }
414
                    }
415
                }
416
417
                $this->ordered_items = self::get_flat_ordered_items_list(
418
                    $this->get_id(),
419
                    0,
420
                    $course_id
421
                );
422
                $this->max_ordered_items = 0;
423
                foreach ($this->ordered_items as $index => $dummy) {
424
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
425
                        $this->max_ordered_items = $index;
426
                    }
427
                }
428
                // TODO: Define the current item better.
429
                $this->first();
430
                if ($debug) {
431
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
432
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
433
                }
434
            } else {
435
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
436
            }
437
        }
438
    }
439
440
    /**
441
     * @return string
442
     */
443
    public function getCourseCode()
444
    {
445
        return $this->cc;
446
    }
447
448
    /**
449
     * @return int
450
     */
451
    public function get_course_int_id()
452
    {
453
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
454
    }
455
456
    /**
457
     * @param $course_id
458
     *
459
     * @return int
460
     */
461
    public function set_course_int_id($course_id)
462
    {
463
        return $this->course_int_id = (int) $course_id;
464
    }
465
466
    /**
467
     * Function rewritten based on old_add_item() from Yannick Warnier.
468
     * Due the fact that users can decide where the item should come, I had to overlook this function and
469
     * I found it better to rewrite it. Old function is still available.
470
     * Added also the possibility to add a description.
471
     *
472
     * @param int    $parent
473
     * @param int    $previous
474
     * @param string $type
475
     * @param int    $id               resource ID (ref)
476
     * @param string $title
477
     * @param string $description
478
     * @param int    $prerequisites
479
     * @param int    $max_time_allowed
480
     * @param int    $userId
481
     *
482
     * @return int
483
     */
484
    public function add_item(
485
        $parent,
486
        $previous,
487
        $type = 'dir',
488
        $id,
489
        $title,
490
        $description,
491
        $prerequisites = 0,
492
        $max_time_allowed = 0,
493
        $userId = 0
494
    ) {
495
        $course_id = $this->course_info['real_id'];
496
        if (empty($course_id)) {
497
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
498
            $this->course_info = api_get_course_info($this->cc);
499
            $course_id = $this->course_info['real_id'];
500
        }
501
        $userId = empty($userId) ? api_get_user_id() : $userId;
502
        $sessionId = api_get_session_id();
503
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
504
        $_course = $this->course_info;
505
        $parent = (int) $parent;
506
        $previous = (int) $previous;
507
        $id = (int) $id;
508
        $max_time_allowed = htmlentities($max_time_allowed);
509
        if (empty($max_time_allowed)) {
510
            $max_time_allowed = 0;
511
        }
512
        $sql = "SELECT COUNT(iid) AS num
513
                FROM $tbl_lp_item
514
                WHERE
515
                    c_id = $course_id AND
516
                    lp_id = ".$this->get_id()." AND
517
                    parent_item_id = $parent ";
518
519
        $res_count = Database::query($sql);
520
        $row = Database::fetch_array($res_count);
521
        $num = $row['num'];
522
523
        $tmp_previous = 0;
524
        $display_order = 0;
525
        $next = 0;
526
        if ($num > 0) {
527
            if (empty($previous)) {
528
                $sql = "SELECT iid, next_item_id, display_order
529
                        FROM $tbl_lp_item
530
                        WHERE
531
                            c_id = $course_id AND
532
                            lp_id = ".$this->get_id()." AND
533
                            parent_item_id = $parent AND
534
                            previous_item_id = 0 OR
535
                            previous_item_id = $parent";
536
                $result = Database::query($sql);
537
                $row = Database::fetch_array($result);
538
                if ($row) {
539
                    $next = $row['iid'];
540
                }
541
            } else {
542
                $previous = (int) $previous;
543
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
544
						FROM $tbl_lp_item
545
                        WHERE
546
                            c_id = $course_id AND
547
                            lp_id = ".$this->get_id()." AND
548
                            id = $previous";
549
                $result = Database::query($sql);
550
                $row = Database::fetch_array($result);
551
                if ($row) {
552
                    $tmp_previous = $row['iid'];
553
                    $next = $row['next_item_id'];
554
                    $display_order = $row['display_order'];
555
                }
556
            }
557
        }
558
559
        $id = (int) $id;
560
        $typeCleaned = Database::escape_string($type);
561
        $max_score = 100;
562
        if ($type === 'quiz' && $id) {
563
            $sql = 'SELECT SUM(ponderation)
564
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
565
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
566
                    ON
567
                        quiz_question.id = quiz_rel_question.question_id AND
568
                        quiz_question.c_id = quiz_rel_question.c_id
569
                    WHERE
570
                        quiz_rel_question.exercice_id = '.$id." AND
571
                        quiz_question.c_id = $course_id AND
572
                        quiz_rel_question.c_id = $course_id ";
573
            $rsQuiz = Database::query($sql);
574
            $max_score = Database::result($rsQuiz, 0, 0);
575
576
            // Disabling the exercise if we add it inside a LP
577
            $exercise = new Exercise($course_id);
578
            $exercise->read($id);
579
            $exercise->disable();
580
            $exercise->save();
581
        }
582
583
        $params = [
584
            'c_id' => $course_id,
585
            'lp_id' => $this->get_id(),
586
            'item_type' => $typeCleaned,
587
            'ref' => '',
588
            'title' => $title,
589
            'description' => $description,
590
            'path' => $id,
591
            'max_score' => $max_score,
592
            'parent_item_id' => $parent,
593
            'previous_item_id' => $previous,
594
            'next_item_id' => (int) $next,
595
            'display_order' => $display_order + 1,
596
            'prerequisite' => $prerequisites,
597
            'max_time_allowed' => $max_time_allowed,
598
            'min_score' => 0,
599
            'launch_data' => '',
600
        ];
601
602
        if ($prerequisites != 0) {
603
            $params['prerequisite'] = $prerequisites;
604
        }
605
606
        $new_item_id = Database::insert($tbl_lp_item, $params);
607
        if ($new_item_id) {
608
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
609
            Database::query($sql);
610
611
            if (!empty($next)) {
612
                $sql = "UPDATE $tbl_lp_item
613
                        SET previous_item_id = $new_item_id
614
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
615
                Database::query($sql);
616
            }
617
618
            // Update the item that should be before the new item.
619
            if (!empty($tmp_previous)) {
620
                $sql = "UPDATE $tbl_lp_item
621
                        SET next_item_id = $new_item_id
622
                        WHERE c_id = $course_id AND id = $tmp_previous";
623
                Database::query($sql);
624
            }
625
626
            // Update all the items after the new item.
627
            $sql = "UPDATE $tbl_lp_item
628
                        SET display_order = display_order + 1
629
                    WHERE
630
                        c_id = $course_id AND
631
                        lp_id = ".$this->get_id()." AND
632
                        iid <> $new_item_id AND
633
                        parent_item_id = $parent AND
634
                        display_order > $display_order";
635
            Database::query($sql);
636
637
            // Update the item that should come after the new item.
638
            $sql = "UPDATE $tbl_lp_item
639
                    SET ref = $new_item_id
640
                    WHERE c_id = $course_id AND iid = $new_item_id";
641
            Database::query($sql);
642
643
            $sql = "UPDATE $tbl_lp_item
644
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
645
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
646
            Database::query($sql);
647
648
            // Upload audio.
649
            if (!empty($_FILES['mp3']['name'])) {
650
                // Create the audio folder if it does not exist yet.
651
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
652
                if (!is_dir($filepath.'audio')) {
653
                    mkdir(
654
                        $filepath.'audio',
655
                        api_get_permissions_for_new_directories()
656
                    );
657
                    $audio_id = add_document(
658
                        $_course,
659
                        '/audio',
660
                        'folder',
661
                        0,
662
                        'audio',
663
                        '',
664
                        0,
665
                        true,
666
                        null,
667
                        $sessionId,
668
                        $userId
669
                    );
670
                    api_item_property_update(
671
                        $_course,
672
                        TOOL_DOCUMENT,
673
                        $audio_id,
674
                        'FolderCreated',
675
                        $userId,
676
                        null,
677
                        null,
678
                        null,
679
                        null,
680
                        $sessionId
681
                    );
682
                    api_item_property_update(
683
                        $_course,
684
                        TOOL_DOCUMENT,
685
                        $audio_id,
686
                        'invisible',
687
                        $userId,
688
                        null,
689
                        null,
690
                        null,
691
                        null,
692
                        $sessionId
693
                    );
694
                }
695
696
                $file_path = handle_uploaded_document(
697
                    $_course,
698
                    $_FILES['mp3'],
699
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
700
                    '/audio',
701
                    $userId,
702
                    '',
703
                    '',
704
                    '',
705
                    '',
706
                    false
707
                );
708
709
                // Getting the filename only.
710
                $file_components = explode('/', $file_path);
711
                $file = $file_components[count($file_components) - 1];
712
713
                // Store the mp3 file in the lp_item table.
714
                $sql = "UPDATE $tbl_lp_item SET
715
                          audio = '".Database::escape_string($file)."'
716
                        WHERE iid = '".intval($new_item_id)."'";
717
                Database::query($sql);
718
            }
719
        }
720
721
        return $new_item_id;
722
    }
723
724
    /**
725
     * Static admin function allowing addition of a learnpath to a course.
726
     *
727
     * @param string $courseCode
728
     * @param string $name
729
     * @param string $description
730
     * @param string $learnpath
731
     * @param string $origin
732
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
733
     * @param string $publicated_on
734
     * @param string $expired_on
735
     * @param int    $categoryId
736
     * @param int    $userId
737
     *
738
     * @return int The new learnpath ID on success, 0 on failure
739
     */
740
    public static function add_lp(
741
        $courseCode,
742
        $name,
743
        $description = '',
744
        $learnpath = 'guess',
745
        $origin = 'zip',
746
        $zipname = '',
747
        $publicated_on = '',
748
        $expired_on = '',
749
        $categoryId = 0,
750
        $userId = 0
751
    ) {
752
        global $charset;
753
754
        if (!empty($courseCode)) {
755
            $courseInfo = api_get_course_info($courseCode);
756
            $course_id = $courseInfo['real_id'];
757
        } else {
758
            $course_id = api_get_course_int_id();
759
            $courseInfo = api_get_course_info();
760
        }
761
762
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
763
        // Check course code exists.
764
        // Check lp_name doesn't exist, otherwise append something.
765
        $i = 0;
766
        $categoryId = (int) $categoryId;
767
        // Session id.
768
        $session_id = api_get_session_id();
769
        $userId = empty($userId) ? api_get_user_id() : $userId;
770
771
        if (empty($publicated_on)) {
772
            $publicated_on = null;
773
        } else {
774
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
775
        }
776
777
        if (empty($expired_on)) {
778
            $expired_on = null;
779
        } else {
780
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
781
        }
782
783
        $check_name = "SELECT * FROM $tbl_lp
784
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
785
        $res_name = Database::query($check_name);
786
787
        while (Database::num_rows($res_name)) {
788
            // There is already one such name, update the current one a bit.
789
            $i++;
790
            $name = $name.' - '.$i;
791
            $check_name = "SELECT * FROM $tbl_lp
792
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
793
            $res_name = Database::query($check_name);
794
        }
795
        // New name does not exist yet; keep it.
796
        // Escape description.
797
        // Kevin: added htmlentities().
798
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
799
        $type = 1;
800
        switch ($learnpath) {
801
            case 'guess':
802
                break;
803
            case 'dokeos':
804
            case 'chamilo':
805
                $type = 1;
806
                break;
807
            case 'aicc':
808
                break;
809
        }
810
811
        switch ($origin) {
812
            case 'zip':
813
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
814
                break;
815
            case 'manual':
816
            default:
817
                $get_max = "SELECT MAX(display_order)
818
                            FROM $tbl_lp WHERE c_id = $course_id";
819
                $res_max = Database::query($get_max);
820
                if (Database::num_rows($res_max) < 1) {
821
                    $dsp = 1;
822
                } else {
823
                    $row = Database::fetch_array($res_max);
824
                    $dsp = $row[0] + 1;
825
                }
826
827
                $params = [
828
                    'c_id' => $course_id,
829
                    'lp_type' => $type,
830
                    'name' => $name,
831
                    'description' => $description,
832
                    'path' => '',
833
                    'default_view_mod' => 'embedded',
834
                    'default_encoding' => 'UTF-8',
835
                    'display_order' => $dsp,
836
                    'content_maker' => 'Chamilo',
837
                    'content_local' => 'local',
838
                    'js_lib' => '',
839
                    'session_id' => $session_id,
840
                    'created_on' => api_get_utc_datetime(),
841
                    'modified_on' => api_get_utc_datetime(),
842
                    'publicated_on' => $publicated_on,
843
                    'expired_on' => $expired_on,
844
                    'category_id' => $categoryId,
845
                    'force_commit' => 0,
846
                    'content_license' => '',
847
                    'debug' => 0,
848
                    'theme' => '',
849
                    'preview_image' => '',
850
                    'author' => '',
851
                    'prerequisite' => 0,
852
                    'hide_toc_frame' => 0,
853
                    'seriousgame_mode' => 0,
854
                    'autolaunch' => 0,
855
                    'max_attempts' => 0,
856
                    'subscribe_users' => 0,
857
                    'accumulate_scorm_time' => 1,
858
                ];
859
                $id = Database::insert($tbl_lp, $params);
860
861
                if ($id > 0) {
862
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
863
                    Database::query($sql);
864
865
                    // Insert into item_property.
866
                    api_item_property_update(
867
                        $courseInfo,
868
                        TOOL_LEARNPATH,
869
                        $id,
870
                        'LearnpathAdded',
871
                        $userId
872
                    );
873
                    api_set_default_visibility(
874
                        $id,
875
                        TOOL_LEARNPATH,
876
                        0,
877
                        $courseInfo,
878
                        $session_id,
879
                        $userId
880
                    );
881
882
                    return $id;
883
                }
884
                break;
885
        }
886
    }
887
888
    /**
889
     * Auto completes the parents of an item in case it's been completed or passed.
890
     *
891
     * @param int $item Optional ID of the item from which to look for parents
892
     */
893
    public function autocomplete_parents($item)
894
    {
895
        $debug = $this->debug;
896
897
        if (empty($item)) {
898
            $item = $this->current;
899
        }
900
901
        $currentItem = $this->getItem($item);
902
        if ($currentItem) {
903
            $parent_id = $currentItem->get_parent();
904
            $parent = $this->getItem($parent_id);
905
            if ($parent) {
906
                // if $item points to an object and there is a parent.
907
                if ($debug) {
908
                    error_log(
909
                        'Autocompleting parent of item '.$item.' '.
910
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
911
                        0
912
                    );
913
                }
914
915
                // New experiment including failed and browsed in completed status.
916
                //$current_status = $currentItem->get_status();
917
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
918
                // Fixes chapter auto complete
919
                if (true) {
920
                    // If the current item is completed or passes or succeeded.
921
                    $updateParentStatus = true;
922
                    if ($debug) {
923
                        error_log('Status of current item is alright');
924
                    }
925
926
                    foreach ($parent->get_children() as $childItemId) {
927
                        $childItem = $this->getItem($childItemId);
928
929
                        // If children was not set try to get the info
930
                        if (empty($childItem->db_item_view_id)) {
931
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
932
                        }
933
934
                        // Check all his brothers (parent's children) for completion status.
935
                        if ($childItemId != $item) {
936
                            if ($debug) {
937
                                error_log(
938
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
939
                                    0
940
                                );
941
                            }
942
                            // Trying completing parents of failed and browsed items as well.
943
                            if ($childItem->status_is(
944
                                [
945
                                    'completed',
946
                                    'passed',
947
                                    'succeeded',
948
                                    'browsed',
949
                                    'failed',
950
                                ]
951
                            )
952
                            ) {
953
                                // Keep completion status to true.
954
                                continue;
955
                            } else {
956
                                if ($debug > 2) {
957
                                    error_log(
958
                                        '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,
959
                                        0
960
                                    );
961
                                }
962
                                $updateParentStatus = false;
963
                                break;
964
                            }
965
                        }
966
                    }
967
968
                    if ($updateParentStatus) {
969
                        // If all the children were completed:
970
                        $parent->set_status('completed');
971
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
972
                        // Force the status to "completed"
973
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
974
                        $this->update_queue[$parent->get_id()] = 'completed';
975
                        if ($debug) {
976
                            error_log(
977
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
978
                                print_r($this->update_queue, 1),
979
                                0
980
                            );
981
                        }
982
                        // Recursive call.
983
                        $this->autocomplete_parents($parent->get_id());
984
                    }
985
                }
986
            } else {
987
                if ($debug) {
988
                    error_log("Parent #$parent_id does not exists");
989
                }
990
            }
991
        } else {
992
            if ($debug) {
993
                error_log("#$item is an item that doesn't have parents");
994
            }
995
        }
996
    }
997
998
    /**
999
     * Closes the current resource.
1000
     *
1001
     * Stops the timer
1002
     * Saves into the database if required
1003
     * Clears the current resource data from this object
1004
     *
1005
     * @return bool True on success, false on failure
1006
     */
1007
    public function close()
1008
    {
1009
        if (empty($this->lp_id)) {
1010
            $this->error = 'Trying to close this learnpath but no ID is set';
1011
1012
            return false;
1013
        }
1014
        $this->current_time_stop = time();
1015
        $this->ordered_items = [];
1016
        $this->index = 0;
1017
        unset($this->lp_id);
1018
        //unset other stuff
1019
        return true;
1020
    }
1021
1022
    /**
1023
     * Static admin function allowing removal of a learnpath.
1024
     *
1025
     * @param array  $courseInfo
1026
     * @param int    $id         Learnpath ID
1027
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1028
     *
1029
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1030
     */
1031
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1032
    {
1033
        $course_id = api_get_course_int_id();
1034
        if (!empty($courseInfo)) {
1035
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1036
        }
1037
1038
        // TODO: Implement a way of getting this to work when the current object is not set.
1039
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1040
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1041
        if (!empty($id) && ($id != $this->lp_id)) {
1042
            return false;
1043
        }
1044
1045
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1046
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1047
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1048
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1049
1050
        // Delete lp item id.
1051
        foreach ($this->items as $lpItemId => $dummy) {
1052
            $sql = "DELETE FROM $lp_item_view
1053
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1054
            Database::query($sql);
1055
        }
1056
1057
        // Proposed by Christophe (nickname: clefevre)
1058
        $sql = "DELETE FROM $lp_item
1059
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1060
        Database::query($sql);
1061
1062
        $sql = "DELETE FROM $lp_view
1063
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1064
        Database::query($sql);
1065
1066
        self::toggle_publish($this->lp_id, 'i');
1067
1068
        if ($this->type == 2 || $this->type == 3) {
1069
            // This is a scorm learning path, delete the files as well.
1070
            $sql = "SELECT path FROM $lp
1071
                    WHERE iid = ".$this->lp_id;
1072
            $res = Database::query($sql);
1073
            if (Database::num_rows($res) > 0) {
1074
                $row = Database::fetch_array($res);
1075
                $path = $row['path'];
1076
                $sql = "SELECT id FROM $lp
1077
                        WHERE
1078
                            c_id = $course_id AND
1079
                            path = '$path' AND
1080
                            iid != ".$this->lp_id;
1081
                $res = Database::query($sql);
1082
                if (Database::num_rows($res) > 0) {
1083
                    // Another learning path uses this directory, so don't delete it.
1084
                    if ($this->debug > 2) {
1085
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1086
                    }
1087
                } else {
1088
                    // No other LP uses that directory, delete it.
1089
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1090
                    // The absolute system path for this course.
1091
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1092
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1093
                        if ($this->debug > 2) {
1094
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1095
                        }
1096
                        // Proposed by Christophe (clefevre).
1097
                        if (strcmp(substr($path, -2), "/.") == 0) {
1098
                            $path = substr($path, 0, -1); // Remove "." at the end.
1099
                        }
1100
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1101
                        rmdirr($course_scorm_dir.$path);
1102
                    }
1103
                }
1104
            }
1105
        }
1106
1107
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1108
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1109
        // Delete tools
1110
        $sql = "DELETE FROM $tbl_tool
1111
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1112
        Database::query($sql);
1113
1114
        $sql = "DELETE FROM $lp
1115
                WHERE iid = ".$this->lp_id;
1116
        Database::query($sql);
1117
        // Updates the display order of all lps.
1118
        $this->update_display_order();
1119
1120
        api_item_property_update(
1121
            api_get_course_info(),
1122
            TOOL_LEARNPATH,
1123
            $this->lp_id,
1124
            'delete',
1125
            api_get_user_id()
1126
        );
1127
1128
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1129
            api_get_course_id(),
1130
            4,
1131
            $id,
1132
            api_get_session_id()
1133
        );
1134
1135
        if ($link_info !== false) {
1136
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1137
        }
1138
1139
        if (api_get_setting('search_enabled') == 'true') {
1140
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1141
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1142
        }
1143
    }
1144
1145
    /**
1146
     * Removes all the children of one item - dangerous!
1147
     *
1148
     * @param int $id Element ID of which children have to be removed
1149
     *
1150
     * @return int Total number of children removed
1151
     */
1152
    public function delete_children_items($id)
1153
    {
1154
        $course_id = $this->course_info['real_id'];
1155
1156
        $num = 0;
1157
        $id = (int) $id;
1158
        if (empty($id) || empty($course_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
        $id = (int) $id;
1189
        // TODO: Implement the resource removal.
1190
        if (empty($id) || empty($course_id)) {
1191
            return false;
1192
        }
1193
        // First select item to get previous, next, and display order.
1194
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1195
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1196
        $res_sel = Database::query($sql_sel);
1197
        if (Database::num_rows($res_sel) < 1) {
1198
            return false;
1199
        }
1200
        $row = Database::fetch_array($res_sel);
1201
        $previous = $row['previous_item_id'];
1202
        $next = $row['next_item_id'];
1203
        $display = $row['display_order'];
1204
        $parent = $row['parent_item_id'];
1205
        $lp = $row['lp_id'];
1206
        // Delete children items.
1207
        $this->delete_children_items($id);
1208
        // Now delete the item.
1209
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1210
        Database::query($sql_del);
1211
        // Now update surrounding items.
1212
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1213
                    WHERE iid = $previous";
1214
        Database::query($sql_upd);
1215
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1216
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1217
        Database::query($sql_upd);
1218
        // Now update all following items with new display order.
1219
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1220
                    WHERE
1221
                        c_id = $course_id AND
1222
                        lp_id = $lp AND
1223
                        parent_item_id = $parent AND
1224
                        display_order > $display";
1225
        Database::query($sql_all);
1226
1227
        //Removing prerequisites since the item will not longer exist
1228
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1229
                    WHERE c_id = $course_id AND prerequisite = $id";
1230
        Database::query($sql_all);
1231
1232
        $sql = "UPDATE $lp_item
1233
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1234
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1235
        Database::query($sql);
1236
1237
        // Remove from search engine if enabled.
1238
        if (api_get_setting('search_enabled') === 'true') {
1239
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1240
            $sql = 'SELECT * FROM %s
1241
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1242
                    LIMIT 1';
1243
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1244
            $res = Database::query($sql);
1245
            if (Database::num_rows($res) > 0) {
1246
                $row2 = Database::fetch_array($res);
1247
                $di = new ChamiloIndexer();
1248
                $di->remove_document($row2['search_did']);
1249
            }
1250
            $sql = 'DELETE FROM %s
1251
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1252
                    LIMIT 1';
1253
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1254
            Database::query($sql);
1255
        }
1256
    }
1257
1258
    /**
1259
     * Updates an item's content in place.
1260
     *
1261
     * @param int    $id               Element ID
1262
     * @param int    $parent           Parent item ID
1263
     * @param int    $previous         Previous item ID
1264
     * @param string $title            Item title
1265
     * @param string $description      Item description
1266
     * @param string $prerequisites    Prerequisites (optional)
1267
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1268
     * @param int    $max_time_allowed
1269
     * @param string $url
1270
     *
1271
     * @return bool True on success, false on error
1272
     */
1273
    public function edit_item(
1274
        $id,
1275
        $parent,
1276
        $previous,
1277
        $title,
1278
        $description,
1279
        $prerequisites = '0',
1280
        $audio = [],
1281
        $max_time_allowed = 0,
1282
        $url = ''
1283
    ) {
1284
        $course_id = api_get_course_int_id();
1285
        $_course = api_get_course_info();
1286
        $id = (int) $id;
1287
1288
        if (empty($max_time_allowed)) {
1289
            $max_time_allowed = 0;
1290
        }
1291
1292
        if (empty($id) || empty($_course)) {
1293
            return false;
1294
        }
1295
1296
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1297
        $sql = "SELECT * FROM $tbl_lp_item
1298
                WHERE iid = $id";
1299
        $res_select = Database::query($sql);
1300
        $row_select = Database::fetch_array($res_select);
1301
        $audio_update_sql = '';
1302
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1303
            // Create the audio folder if it does not exist yet.
1304
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1305
            if (!is_dir($filepath.'audio')) {
1306
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1307
                $audio_id = add_document(
1308
                    $_course,
1309
                    '/audio',
1310
                    'folder',
1311
                    0,
1312
                    'audio'
1313
                );
1314
                api_item_property_update(
1315
                    $_course,
1316
                    TOOL_DOCUMENT,
1317
                    $audio_id,
1318
                    'FolderCreated',
1319
                    api_get_user_id(),
1320
                    null,
1321
                    null,
1322
                    null,
1323
                    null,
1324
                    api_get_session_id()
1325
                );
1326
                api_item_property_update(
1327
                    $_course,
1328
                    TOOL_DOCUMENT,
1329
                    $audio_id,
1330
                    'invisible',
1331
                    api_get_user_id(),
1332
                    null,
1333
                    null,
1334
                    null,
1335
                    null,
1336
                    api_get_session_id()
1337
                );
1338
            }
1339
1340
            // Upload file in documents.
1341
            $pi = pathinfo($audio['name']);
1342
            if ($pi['extension'] === 'mp3') {
1343
                $c_det = api_get_course_info($this->cc);
1344
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1345
                $path = handle_uploaded_document(
1346
                    $c_det,
1347
                    $audio,
1348
                    $bp,
1349
                    '/audio',
1350
                    api_get_user_id(),
1351
                    0,
1352
                    null,
1353
                    0,
1354
                    'rename',
1355
                    false,
1356
                    0
1357
                );
1358
                $path = substr($path, 7);
1359
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1360
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1361
            }
1362
        }
1363
1364
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1365
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1366
1367
        // TODO: htmlspecialchars to be checked for encoding related problems.
1368
        if ($same_parent && $same_previous) {
1369
            // Only update title and description.
1370
            $sql = "UPDATE $tbl_lp_item
1371
                    SET title = '".Database::escape_string($title)."',
1372
                        prerequisite = '".$prerequisites."',
1373
                        description = '".Database::escape_string($description)."'
1374
                        ".$audio_update_sql.",
1375
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1376
                    WHERE iid = $id";
1377
            Database::query($sql);
1378
        } else {
1379
            $old_parent = $row_select['parent_item_id'];
1380
            $old_previous = $row_select['previous_item_id'];
1381
            $old_next = $row_select['next_item_id'];
1382
            $old_order = $row_select['display_order'];
1383
            $old_prerequisite = $row_select['prerequisite'];
1384
            $old_max_time_allowed = $row_select['max_time_allowed'];
1385
1386
            /* BEGIN -- virtually remove the current item id */
1387
            /* for the next and previous item it is like the current item doesn't exist anymore */
1388
            if ($old_previous != 0) {
1389
                // Next
1390
                $sql = "UPDATE $tbl_lp_item
1391
                        SET next_item_id = $old_next
1392
                        WHERE iid = $old_previous";
1393
                Database::query($sql);
1394
            }
1395
1396
            if (!empty($old_next)) {
1397
                // Previous
1398
                $sql = "UPDATE $tbl_lp_item
1399
                        SET previous_item_id = $old_previous
1400
                        WHERE iid = $old_next";
1401
                Database::query($sql);
1402
            }
1403
1404
            // display_order - 1 for every item with a display_order
1405
            // bigger then the display_order of the current item.
1406
            $sql = "UPDATE $tbl_lp_item
1407
                    SET display_order = display_order - 1
1408
                    WHERE
1409
                        c_id = $course_id AND
1410
                        display_order > $old_order AND
1411
                        lp_id = ".$this->lp_id." AND
1412
                        parent_item_id = $old_parent";
1413
            Database::query($sql);
1414
            /* END -- virtually remove the current item id */
1415
1416
            /* BEGIN -- update the current item id to his new location */
1417
            if ($previous == 0) {
1418
                // Select the data of the item that should come after the current item.
1419
                $sql = "SELECT id, display_order
1420
                        FROM $tbl_lp_item
1421
                        WHERE
1422
                            c_id = $course_id AND
1423
                            lp_id = ".$this->lp_id." AND
1424
                            parent_item_id = $parent AND
1425
                            previous_item_id = $previous";
1426
                $res_select_old = Database::query($sql);
1427
                $row_select_old = Database::fetch_array($res_select_old);
1428
1429
                // If the new parent didn't have children before.
1430
                if (Database::num_rows($res_select_old) == 0) {
1431
                    $new_next = 0;
1432
                    $new_order = 1;
1433
                } else {
1434
                    $new_next = $row_select_old['id'];
1435
                    $new_order = $row_select_old['display_order'];
1436
                }
1437
            } else {
1438
                // Select the data of the item that should come before the current item.
1439
                $sql = "SELECT next_item_id, display_order
1440
                        FROM $tbl_lp_item
1441
                        WHERE iid = $previous";
1442
                $res_select_old = Database::query($sql);
1443
                $row_select_old = Database::fetch_array($res_select_old);
1444
                $new_next = $row_select_old['next_item_id'];
1445
                $new_order = $row_select_old['display_order'] + 1;
1446
            }
1447
1448
            // TODO: htmlspecialchars to be checked for encoding related problems.
1449
            // Update the current item with the new data.
1450
            $sql = "UPDATE $tbl_lp_item
1451
                    SET
1452
                        title = '".Database::escape_string($title)."',
1453
                        description = '".Database::escape_string($description)."',
1454
                        parent_item_id = $parent,
1455
                        previous_item_id = $previous,
1456
                        next_item_id = $new_next,
1457
                        display_order = $new_order
1458
                        $audio_update_sql
1459
                    WHERE iid = $id";
1460
            Database::query($sql);
1461
1462
            if ($previous != 0) {
1463
                // Update the previous item's next_item_id.
1464
                $sql = "UPDATE $tbl_lp_item
1465
                        SET next_item_id = $id
1466
                        WHERE iid = $previous";
1467
                Database::query($sql);
1468
            }
1469
1470
            if (!empty($new_next)) {
1471
                // Update the next item's previous_item_id.
1472
                $sql = "UPDATE $tbl_lp_item
1473
                        SET previous_item_id = $id
1474
                        WHERE iid = $new_next";
1475
                Database::query($sql);
1476
            }
1477
1478
            if ($old_prerequisite != $prerequisites) {
1479
                $sql = "UPDATE $tbl_lp_item
1480
                        SET prerequisite = '$prerequisites'
1481
                        WHERE iid = $id";
1482
                Database::query($sql);
1483
            }
1484
1485
            if ($old_max_time_allowed != $max_time_allowed) {
1486
                // update max time allowed
1487
                $sql = "UPDATE $tbl_lp_item
1488
                        SET max_time_allowed = $max_time_allowed
1489
                        WHERE iid = $id";
1490
                Database::query($sql);
1491
            }
1492
1493
            // Update all the items with the same or a bigger display_order than the current item.
1494
            $sql = "UPDATE $tbl_lp_item
1495
                    SET display_order = display_order + 1
1496
                    WHERE
1497
                       c_id = $course_id AND
1498
                       lp_id = ".$this->get_id()." AND
1499
                       iid <> $id AND
1500
                       parent_item_id = $parent AND
1501
                       display_order >= $new_order";
1502
            Database::query($sql);
1503
        }
1504
1505
        if ($row_select['item_type'] == 'link') {
1506
            $link = new Link();
1507
            $linkId = $row_select['path'];
1508
            $link->updateLink($linkId, $url);
1509
        }
1510
    }
1511
1512
    /**
1513
     * Updates an item's prereq in place.
1514
     *
1515
     * @param int    $id              Element ID
1516
     * @param string $prerequisite_id Prerequisite Element ID
1517
     * @param int    $minScore        Prerequisite min score
1518
     * @param int    $maxScore        Prerequisite max score
1519
     *
1520
     * @return bool True on success, false on error
1521
     */
1522
    public function edit_item_prereq(
1523
        $id,
1524
        $prerequisite_id,
1525
        $minScore = 0,
1526
        $maxScore = 100
1527
    ) {
1528
        $id = (int) $id;
1529
        $prerequisite_id = (int) $prerequisite_id;
1530
1531
        if (empty($id)) {
1532
            return false;
1533
        }
1534
1535
        if (empty($minScore) || $minScore < 0) {
1536
            $minScore = 0;
1537
        }
1538
1539
        if (empty($maxScore) || $maxScore < 0) {
1540
            $maxScore = 100;
1541
        }
1542
1543
        $minScore = floatval($minScore);
1544
        $maxScore = floatval($maxScore);
1545
1546
        if (empty($prerequisite_id)) {
1547
            $prerequisite_id = 'NULL';
1548
            $minScore = 0;
1549
            $maxScore = 100;
1550
        }
1551
1552
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1553
        $sql = " UPDATE $tbl_lp_item
1554
                 SET
1555
                    prerequisite = $prerequisite_id ,
1556
                    prerequisite_min_score = $minScore ,
1557
                    prerequisite_max_score = $maxScore
1558
                 WHERE iid = $id";
1559
1560
        Database::query($sql);
1561
1562
        return true;
1563
    }
1564
1565
    /**
1566
     * Get the specific prefix index terms of this learning path.
1567
     *
1568
     * @param string $prefix
1569
     *
1570
     * @return array Array of terms
1571
     */
1572
    public function get_common_index_terms_by_prefix($prefix)
1573
    {
1574
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1575
        $terms = get_specific_field_values_list_by_prefix(
1576
            $prefix,
1577
            $this->cc,
1578
            TOOL_LEARNPATH,
1579
            $this->lp_id
1580
        );
1581
        $prefix_terms = [];
1582
        if (!empty($terms)) {
1583
            foreach ($terms as $term) {
1584
                $prefix_terms[] = $term['value'];
1585
            }
1586
        }
1587
1588
        return $prefix_terms;
1589
    }
1590
1591
    /**
1592
     * Gets the number of items currently completed.
1593
     *
1594
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1595
     *
1596
     * @return int The number of items currently completed
1597
     */
1598
    public function get_complete_items_count($failedStatusException = false)
1599
    {
1600
        $i = 0;
1601
        $completedStatusList = [
1602
            'completed',
1603
            'passed',
1604
            'succeeded',
1605
            'browsed',
1606
        ];
1607
1608
        if (!$failedStatusException) {
1609
            $completedStatusList[] = 'failed';
1610
        }
1611
1612
        foreach ($this->items as $id => $dummy) {
1613
            // Trying failed and browsed considered "progressed" as well.
1614
            if ($this->items[$id]->status_is($completedStatusList) &&
1615
                $this->items[$id]->get_type() != 'dir'
1616
            ) {
1617
                $i++;
1618
            }
1619
        }
1620
1621
        return $i;
1622
    }
1623
1624
    /**
1625
     * Gets the current item ID.
1626
     *
1627
     * @return int The current learnpath item id
1628
     */
1629
    public function get_current_item_id()
1630
    {
1631
        $current = 0;
1632
        if (!empty($this->current)) {
1633
            $current = (int) $this->current;
1634
        }
1635
1636
        return $current;
1637
    }
1638
1639
    /**
1640
     * Force to get the first learnpath item id.
1641
     *
1642
     * @return int The current learnpath item id
1643
     */
1644
    public function get_first_item_id()
1645
    {
1646
        $current = 0;
1647
        if (is_array($this->ordered_items)) {
1648
            $current = $this->ordered_items[0];
1649
        }
1650
1651
        return $current;
1652
    }
1653
1654
    /**
1655
     * Gets the total number of items available for viewing in this SCORM.
1656
     *
1657
     * @return int The total number of items
1658
     */
1659
    public function get_total_items_count()
1660
    {
1661
        return count($this->items);
1662
    }
1663
1664
    /**
1665
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1666
     *
1667
     * @return int The total no-chapters number of items
1668
     */
1669
    public function getTotalItemsCountWithoutDirs()
1670
    {
1671
        $total = 0;
1672
        $typeListNotToCount = self::getChapterTypes();
1673
        foreach ($this->items as $temp2) {
1674
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1675
                $total++;
1676
            }
1677
        }
1678
1679
        return $total;
1680
    }
1681
1682
    /**
1683
     *  Sets the first element URL.
1684
     */
1685
    public function first()
1686
    {
1687
        if ($this->debug > 0) {
1688
            error_log('In learnpath::first()', 0);
1689
            error_log('$this->last_item_seen '.$this->last_item_seen);
1690
        }
1691
1692
        // Test if the last_item_seen exists and is not a dir.
1693
        if (count($this->ordered_items) == 0) {
1694
            $this->index = 0;
1695
        }
1696
1697
        if (!empty($this->last_item_seen) &&
1698
            !empty($this->items[$this->last_item_seen]) &&
1699
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1700
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1701
            //&& !$this->items[$this->last_item_seen]->is_done()
1702
        ) {
1703
            if ($this->debug > 2) {
1704
                error_log(
1705
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1706
                    $this->items[$this->last_item_seen]->get_type()
1707
                );
1708
            }
1709
            $index = -1;
1710
            foreach ($this->ordered_items as $myindex => $item_id) {
1711
                if ($item_id == $this->last_item_seen) {
1712
                    $index = $myindex;
1713
                    break;
1714
                }
1715
            }
1716
            if ($index == -1) {
1717
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1718
                if ($this->debug > 2) {
1719
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1720
                }
1721
1722
                return false;
1723
            } else {
1724
                $this->last = $this->last_item_seen;
1725
                $this->current = $this->last_item_seen;
1726
                $this->index = $index;
1727
            }
1728
        } else {
1729
            if ($this->debug > 2) {
1730
                error_log('In learnpath::first() - No last item seen', 0);
1731
            }
1732
            $index = 0;
1733
            // Loop through all ordered items and stop at the first item that is
1734
            // not a directory *and* that has not been completed yet.
1735
            while (!empty($this->ordered_items[$index]) &&
1736
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1737
                (
1738
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1739
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1740
                ) && $index < $this->max_ordered_items) {
1741
                $index++;
1742
            }
1743
1744
            $this->last = $this->current;
1745
            // current is
1746
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1747
            $this->index = $index;
1748
            if ($this->debug > 2) {
1749
                error_log('$index '.$index);
1750
                error_log('In learnpath::first() - No last item seen');
1751
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1752
            }
1753
        }
1754
        if ($this->debug > 2) {
1755
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1756
        }
1757
    }
1758
1759
    /**
1760
     * Gets the js library from the database.
1761
     *
1762
     * @return string The name of the javascript library to be used
1763
     */
1764
    public function get_js_lib()
1765
    {
1766
        $lib = '';
1767
        if (!empty($this->js_lib)) {
1768
            $lib = $this->js_lib;
1769
        }
1770
1771
        return $lib;
1772
    }
1773
1774
    /**
1775
     * Gets the learnpath database ID.
1776
     *
1777
     * @return int Learnpath ID in the lp table
1778
     */
1779
    public function get_id()
1780
    {
1781
        if (!empty($this->lp_id)) {
1782
            return (int) $this->lp_id;
1783
        }
1784
1785
        return 0;
1786
    }
1787
1788
    /**
1789
     * Gets the last element URL.
1790
     *
1791
     * @return string URL to load into the viewer
1792
     */
1793
    public function get_last()
1794
    {
1795
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1796
        if (count($this->ordered_items) > 0) {
1797
            $this->index = count($this->ordered_items) - 1;
1798
1799
            return $this->ordered_items[$this->index];
1800
        }
1801
1802
        return false;
1803
    }
1804
1805
    /**
1806
     * Get the last element in the first level.
1807
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1808
     *
1809
     * @return mixed
1810
     */
1811
    public function getLastInFirstLevel()
1812
    {
1813
        try {
1814
            $lastId = Database::getManager()
1815
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1816
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1817
                ->setMaxResults(1)
1818
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1819
                ->getSingleScalarResult();
1820
1821
            return $lastId;
1822
        } catch (Exception $exception) {
1823
            return 0;
1824
        }
1825
    }
1826
1827
    /**
1828
     * Gets the navigation bar for the learnpath display screen.
1829
     *
1830
     * @param string $barId
1831
     *
1832
     * @return string The HTML string to use as a navigation bar
1833
     */
1834
    public function get_navigation_bar($barId = '')
1835
    {
1836
        if (empty($barId)) {
1837
            $barId = 'control-top';
1838
        }
1839
        $lpId = $this->lp_id;
1840
        $mycurrentitemid = $this->get_current_item_id();
1841
1842
        $reportingText = get_lang('Reporting');
1843
        $previousText = get_lang('ScormPrevious');
1844
        $nextText = get_lang('ScormNext');
1845
        $fullScreenText = get_lang('ScormExitFullScreen');
1846
1847
        $settings = api_get_configuration_value('lp_view_settings');
1848
        $display = isset($settings['display']) ? $settings['display'] : false;
1849
        $reportingIcon = '
1850
            <a class="icon-toolbar"
1851
                id="stats_link"
1852
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1853
                onclick="window.parent.API.save_asset(); return true;"
1854
                target="content_name" title="'.$reportingText.'">
1855
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1856
            </a>';
1857
1858
        if (!empty($display)) {
1859
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1860
            if ($showReporting === false) {
1861
                $reportingIcon = '';
1862
            }
1863
        }
1864
1865
        $hideArrows = false;
1866
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1867
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1868
        }
1869
1870
        $previousIcon = '';
1871
        $nextIcon = '';
1872
        if ($hideArrows === false) {
1873
            $previousIcon = '
1874
                <a class="icon-toolbar" id="scorm-previous" href="#"
1875
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1876
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1877
                </a>';
1878
1879
            $nextIcon = '
1880
                <a class="icon-toolbar" id="scorm-next" href="#"
1881
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1882
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1883
                </a>';
1884
        }
1885
1886
        if ($this->mode === 'fullscreen') {
1887
            $navbar = '
1888
                  <span id="'.$barId.'" class="buttons">
1889
                    '.$reportingIcon.'
1890
                    '.$previousIcon.'
1891
                    '.$nextIcon.'
1892
                    <a class="icon-toolbar" id="view-embedded"
1893
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1894
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1895
                    </a>
1896
                  </span>';
1897
        } else {
1898
            $navbar = '
1899
                 <span id="'.$barId.'" class="buttons text-right">
1900
                    '.$reportingIcon.'
1901
                    '.$previousIcon.'
1902
                    '.$nextIcon.'
1903
                </span>';
1904
        }
1905
1906
        return $navbar;
1907
    }
1908
1909
    /**
1910
     * Gets the next resource in queue (url).
1911
     *
1912
     * @return string URL to load into the viewer
1913
     */
1914
    public function get_next_index()
1915
    {
1916
        // TODO
1917
        $index = $this->index;
1918
        $index++;
1919
        while (
1920
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1921
            $index < $this->max_ordered_items
1922
        ) {
1923
            $index++;
1924
            if ($index == $this->max_ordered_items) {
1925
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1926
                    return $this->index;
1927
                }
1928
1929
                return $index;
1930
            }
1931
        }
1932
        if (empty($this->ordered_items[$index])) {
1933
            return $this->index;
1934
        }
1935
1936
        return $index;
1937
    }
1938
1939
    /**
1940
     * Gets item_id for the next element.
1941
     *
1942
     * @return int Next item (DB) ID
1943
     */
1944
    public function get_next_item_id()
1945
    {
1946
        $new_index = $this->get_next_index();
1947
        if (!empty($new_index)) {
1948
            if (isset($this->ordered_items[$new_index])) {
1949
                return $this->ordered_items[$new_index];
1950
            }
1951
        }
1952
1953
        return 0;
1954
    }
1955
1956
    /**
1957
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1958
     *
1959
     * Generally, the package provided is in the form of a zip file, so the function
1960
     * has been written to test a zip file. If not a zip, the function will return the
1961
     * default return value: ''
1962
     *
1963
     * @param string $file_path the path to the file
1964
     * @param string $file_name the original name of the file
1965
     *
1966
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1967
     */
1968
    public static function getPackageType($file_path, $file_name)
1969
    {
1970
        // Get name of the zip file without the extension.
1971
        $file_info = pathinfo($file_name);
1972
        $extension = $file_info['extension']; // Extension only.
1973
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1974
                'dll',
1975
                'exe',
1976
            ])) {
1977
            return 'oogie';
1978
        }
1979
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1980
                'dll',
1981
                'exe',
1982
            ])) {
1983
            return 'woogie';
1984
        }
1985
1986
        $zipFile = new PclZip($file_path);
1987
        // Check the zip content (real size and file extension).
1988
        $zipContentArray = $zipFile->listContent();
1989
        $package_type = '';
1990
        $manifest = '';
1991
        $aicc_match_crs = 0;
1992
        $aicc_match_au = 0;
1993
        $aicc_match_des = 0;
1994
        $aicc_match_cst = 0;
1995
        $countItems = 0;
1996
1997
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1998
        if (is_array($zipContentArray)) {
1999
            $countItems = count($zipContentArray);
2000
            if ($countItems > 0) {
2001
                foreach ($zipContentArray as $thisContent) {
2002
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2003
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2004
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2005
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2006
                        $package_type = 'scorm';
2007
                        break; // Exit the foreach loop.
2008
                    } elseif (
2009
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2010
                        in_array(
2011
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2012
                            ['crs', 'au', 'des', 'cst']
2013
                        )
2014
                    ) {
2015
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2016
                        switch ($ext) {
2017
                            case 'crs':
2018
                                $aicc_match_crs = 1;
2019
                                break;
2020
                            case 'au':
2021
                                $aicc_match_au = 1;
2022
                                break;
2023
                            case 'des':
2024
                                $aicc_match_des = 1;
2025
                                break;
2026
                            case 'cst':
2027
                                $aicc_match_cst = 1;
2028
                                break;
2029
                            default:
2030
                                break;
2031
                        }
2032
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2033
                    } else {
2034
                        $package_type = '';
2035
                    }
2036
                }
2037
            }
2038
        }
2039
2040
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2041
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2042
            $package_type = 'aicc';
2043
        }
2044
2045
        // Try with chamilo course builder
2046
        if (empty($package_type)) {
2047
            // Sometimes users will try to upload an empty zip, or a zip with
2048
            // only a folder. Catch that and make the calling function aware.
2049
            // If the single file was the imsmanifest.xml, then $package_type
2050
            // would be 'scorm' and we wouldn't be here.
2051
            if ($countItems < 2) {
2052
                return 'error-empty-package';
2053
            }
2054
            $package_type = 'chamilo';
2055
        }
2056
2057
        return $package_type;
2058
    }
2059
2060
    /**
2061
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2062
     *
2063
     * @return string URL to load into the viewer
2064
     */
2065
    public function get_previous_index()
2066
    {
2067
        $index = $this->index;
2068
        if (isset($this->ordered_items[$index - 1])) {
2069
            $index--;
2070
            while (isset($this->ordered_items[$index]) &&
2071
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2072
            ) {
2073
                $index--;
2074
                if ($index < 0) {
2075
                    return $this->index;
2076
                }
2077
            }
2078
        }
2079
2080
        return $index;
2081
    }
2082
2083
    /**
2084
     * Gets item_id for the next element.
2085
     *
2086
     * @return int Previous item (DB) ID
2087
     */
2088
    public function get_previous_item_id()
2089
    {
2090
        $index = $this->get_previous_index();
2091
2092
        return $this->ordered_items[$index];
2093
    }
2094
2095
    /**
2096
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2097
     *
2098
     * @param int    $lpItemId
2099
     * @param string $autostart
2100
     *
2101
     * @return string The mediaplayer HTML
2102
     */
2103
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2104
    {
2105
        $course_id = api_get_course_int_id();
2106
        $_course = api_get_course_info();
2107
        if (empty($_course)) {
2108
            return '';
2109
        }
2110
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2111
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2112
        $lpItemId = (int) $lpItemId;
2113
2114
        /** @var learnpathItem $item */
2115
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2116
        $itemViewId = 0;
2117
        if ($item) {
2118
            $itemViewId = (int) $item->db_item_view_id;
2119
        }
2120
2121
        // Getting all the information about the item.
2122
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status
2123
                FROM $tbl_lp_item as lpi
2124
                INNER JOIN $tbl_lp_item_view as lp_view
2125
                ON (lpi.iid = lp_view.lp_item_id)
2126
                WHERE
2127
                    lp_view.iid = $itemViewId AND
2128
                    lpi.iid = $lpItemId AND
2129
                    lp_view.c_id = $course_id";
2130
        $result = Database::query($sql);
2131
        $row = Database::fetch_assoc($result);
2132
        $output = '';
2133
2134
        if (!empty($row['audio'])) {
2135
            $list = $_SESSION['oLP']->get_toc();
2136
2137
            switch ($row['item_type']) {
2138
                case 'quiz':
2139
                    $type_quiz = false;
2140
                    foreach ($list as $toc) {
2141
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2142
                            $type_quiz = true;
2143
                        }
2144
                    }
2145
2146
                    if ($type_quiz) {
2147
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2148
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2149
                        } else {
2150
                            $autostart_audio = $autostart;
2151
                        }
2152
                    }
2153
                    break;
2154
                case TOOL_READOUT_TEXT:;
2155
                    $autostart_audio = 'false';
2156
                    break;
2157
                default:
2158
                    $autostart_audio = 'true';
2159
            }
2160
2161
            $courseInfo = api_get_course_info();
2162
            $audio = $row['audio'];
2163
2164
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2165
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2166
2167
            if (!file_exists($file)) {
2168
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2169
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2170
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2171
            }
2172
2173
            $player = Display::getMediaPlayer(
2174
                $file,
2175
                [
2176
                    'id' => 'lp_audio_media_player',
2177
                    'url' => $url,
2178
                    'autoplay' => $autostart_audio,
2179
                    'width' => '100%',
2180
                ]
2181
            );
2182
2183
            // The mp3 player.
2184
            $output = '<div id="container">';
2185
            $output .= $player;
2186
            $output .= '</div>';
2187
        }
2188
2189
        return $output;
2190
    }
2191
2192
    /**
2193
     * @param int   $studentId
2194
     * @param int   $prerequisite
2195
     * @param array $courseInfo
2196
     * @param int   $sessionId
2197
     *
2198
     * @return bool
2199
     */
2200
    public static function isBlockedByPrerequisite(
2201
        $studentId,
2202
        $prerequisite,
2203
        $courseInfo,
2204
        $sessionId
2205
    ) {
2206
        if (empty($courseInfo)) {
2207
            return false;
2208
        }
2209
2210
        $courseId = $courseInfo['real_id'];
2211
2212
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2213
        if ($allow) {
2214
            if (api_is_allowed_to_edit() ||
2215
                api_is_platform_admin(true) ||
2216
                api_is_drh() ||
2217
                api_is_coach($sessionId, $courseId, false)
2218
            ) {
2219
                return false;
2220
            }
2221
        }
2222
2223
        $isBlocked = false;
2224
        if (!empty($prerequisite)) {
2225
            $progress = self::getProgress(
2226
                $prerequisite,
2227
                $studentId,
2228
                $courseId,
2229
                $sessionId
2230
            );
2231
            if ($progress < 100) {
2232
                $isBlocked = true;
2233
            }
2234
2235
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2236
                // Block if it does not exceed minimum time
2237
                // Minimum time (in minutes) to pass the learning path
2238
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2239
2240
                if ($accumulateWorkTime > 0) {
2241
                    // Total time in course (sum of times in learning paths from course)
2242
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2243
2244
                    // Connect with the plugin_licences_course_session table
2245
                    // which indicates what percentage of the time applies
2246
                    // Minimum connection percentage
2247
                    $perc = 100;
2248
                    // Time from the course
2249
                    $tc = $accumulateWorkTimeTotal;
2250
2251
                    // Percentage of the learning paths
2252
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2253
                    // Minimum time for each learning path
2254
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2255
2256
                    // Spent time (in seconds) so far in the learning path
2257
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2258
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2259
2260
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2261
                        $isBlocked = true;
2262
                    }
2263
                }
2264
            }
2265
        }
2266
2267
        return $isBlocked;
2268
    }
2269
2270
    /**
2271
     * Checks if the learning path is visible for student after the progress
2272
     * of its prerequisite is completed, considering the time availability and
2273
     * the LP visibility.
2274
     *
2275
     * @param int   $lp_id
2276
     * @param int   $student_id
2277
     * @param array $courseInfo
2278
     * @param int   $sessionId
2279
     *
2280
     * @return bool
2281
     */
2282
    public static function is_lp_visible_for_student(
2283
        $lp_id,
2284
        $student_id,
2285
        $courseInfo = [],
2286
        $sessionId = 0
2287
    ) {
2288
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2289
        $lp_id = (int) $lp_id;
2290
        $sessionId = (int) $sessionId;
2291
2292
        if (empty($courseInfo)) {
2293
            return false;
2294
        }
2295
2296
        if (empty($sessionId)) {
2297
            $sessionId = api_get_session_id();
2298
        }
2299
2300
        $courseId = $courseInfo['real_id'];
2301
2302
        $itemInfo = api_get_item_property_info(
2303
            $courseId,
2304
            TOOL_LEARNPATH,
2305
            $lp_id,
2306
            $sessionId
2307
        );
2308
2309
        // If the item was deleted.
2310
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2311
            return false;
2312
        }
2313
2314
        // @todo remove this query and load the row info as a parameter
2315
        $table = Database::get_course_table(TABLE_LP_MAIN);
2316
        // Get current prerequisite
2317
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2318
                FROM $table
2319
                WHERE iid = $lp_id";
2320
        $rs = Database::query($sql);
2321
        $now = time();
2322
        if (Database::num_rows($rs) > 0) {
2323
            $row = Database::fetch_array($rs, 'ASSOC');
2324
2325
            if (!empty($row['category_id'])) {
2326
                $em = Database::getManager();
2327
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2328
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2329
                    return false;
2330
                }
2331
            }
2332
2333
            $prerequisite = $row['prerequisite'];
2334
            $is_visible = true;
2335
2336
            $isBlocked = self::isBlockedByPrerequisite(
2337
                $student_id,
2338
                $prerequisite,
2339
                $courseInfo,
2340
                $sessionId
2341
            );
2342
2343
            if ($isBlocked) {
2344
                $is_visible = false;
2345
            }
2346
2347
            // Also check the time availability of the LP
2348
            if ($is_visible) {
2349
                // Adding visibility restrictions
2350
                if (!empty($row['publicated_on'])) {
2351
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2352
                        $is_visible = false;
2353
                    }
2354
                }
2355
                // Blocking empty start times see BT#2800
2356
                global $_custom;
2357
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2358
                    $_custom['lps_hidden_when_no_start_date']
2359
                ) {
2360
                    if (empty($row['publicated_on'])) {
2361
                        $is_visible = false;
2362
                    }
2363
                }
2364
2365
                if (!empty($row['expired_on'])) {
2366
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2367
                        $is_visible = false;
2368
                    }
2369
                }
2370
            }
2371
2372
            if ($is_visible) {
2373
                $subscriptionSettings = self::getSubscriptionSettings();
2374
2375
                // Check if the subscription users/group to a LP is ON
2376
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2377
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2378
                ) {
2379
                    // Try group
2380
                    $is_visible = false;
2381
                    // Checking only the user visibility
2382
                    $userVisibility = api_get_item_visibility(
2383
                        $courseInfo,
2384
                        'learnpath',
2385
                        $row['id'],
2386
                        $sessionId,
2387
                        $student_id,
2388
                        'LearnpathSubscription'
2389
                    );
2390
2391
                    if ($userVisibility == 1) {
2392
                        $is_visible = true;
2393
                    } else {
2394
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2395
                        if (!empty($userGroups)) {
2396
                            foreach ($userGroups as $groupInfo) {
2397
                                $groupId = $groupInfo['iid'];
2398
                                $userVisibility = api_get_item_visibility(
2399
                                    $courseInfo,
2400
                                    'learnpath',
2401
                                    $row['id'],
2402
                                    $sessionId,
2403
                                    null,
2404
                                    'LearnpathSubscription',
2405
                                    $groupId
2406
                                );
2407
2408
                                if ($userVisibility == 1) {
2409
                                    $is_visible = true;
2410
                                    break;
2411
                                }
2412
                            }
2413
                        }
2414
                    }
2415
                }
2416
            }
2417
2418
            return $is_visible;
2419
        }
2420
2421
        return false;
2422
    }
2423
2424
    /**
2425
     * @param int $lpId
2426
     * @param int $userId
2427
     * @param int $courseId
2428
     * @param int $sessionId
2429
     *
2430
     * @return int
2431
     */
2432
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2433
    {
2434
        $lpId = (int) $lpId;
2435
        $userId = (int) $userId;
2436
        $courseId = (int) $courseId;
2437
        $sessionId = (int) $sessionId;
2438
2439
        $sessionCondition = api_get_session_condition($sessionId);
2440
        $table = Database::get_course_table(TABLE_LP_VIEW);
2441
        $sql = "SELECT progress FROM $table
2442
                WHERE
2443
                    c_id = $courseId AND
2444
                    lp_id = $lpId AND
2445
                    user_id = $userId $sessionCondition ";
2446
        $res = Database::query($sql);
2447
2448
        $progress = 0;
2449
        if (Database::num_rows($res) > 0) {
2450
            $row = Database::fetch_array($res);
2451
            $progress = (int) $row['progress'];
2452
        }
2453
2454
        return $progress;
2455
    }
2456
2457
    /**
2458
     * @param array $lpList
2459
     * @param int   $userId
2460
     * @param int   $courseId
2461
     * @param int   $sessionId
2462
     *
2463
     * @return array
2464
     */
2465
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2466
    {
2467
        $lpList = array_map('intval', $lpList);
2468
        if (empty($lpList)) {
2469
            return [];
2470
        }
2471
2472
        $lpList = implode("','", $lpList);
2473
2474
        $userId = (int) $userId;
2475
        $courseId = (int) $courseId;
2476
        $sessionId = (int) $sessionId;
2477
2478
        $sessionCondition = api_get_session_condition($sessionId);
2479
        $table = Database::get_course_table(TABLE_LP_VIEW);
2480
        $sql = "SELECT lp_id, progress FROM $table
2481
                WHERE
2482
                    c_id = $courseId AND
2483
                    lp_id IN ('".$lpList."') AND
2484
                    user_id = $userId $sessionCondition ";
2485
        $res = Database::query($sql);
2486
2487
        if (Database::num_rows($res) > 0) {
2488
            $list = [];
2489
            while ($row = Database::fetch_array($res)) {
2490
                $list[$row['lp_id']] = $row['progress'];
2491
            }
2492
2493
            return $list;
2494
        }
2495
2496
        return [];
2497
    }
2498
2499
    /**
2500
     * Displays a progress bar
2501
     * completed so far.
2502
     *
2503
     * @param int    $percentage Progress value to display
2504
     * @param string $text_add   Text to display near the progress value
2505
     *
2506
     * @return string HTML string containing the progress bar
2507
     */
2508
    public static function get_progress_bar($percentage = -1, $text_add = '')
2509
    {
2510
        $text = $percentage.$text_add;
2511
        $output = '<div class="progress">
2512
            <div id="progress_bar_value"
2513
                class="progress-bar progress-bar-success" role="progressbar"
2514
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2515
            '.$text.'
2516
            </div>
2517
        </div>';
2518
2519
        return $output;
2520
    }
2521
2522
    /**
2523
     * @param string $mode can be '%' or 'abs'
2524
     *                     otherwise this value will be used $this->progress_bar_mode
2525
     *
2526
     * @return string
2527
     */
2528
    public function getProgressBar($mode = null)
2529
    {
2530
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2531
2532
        return self::get_progress_bar($percentage, $text_add);
2533
    }
2534
2535
    /**
2536
     * Gets the progress bar info to display inside the progress bar.
2537
     * Also used by scorm_api.php.
2538
     *
2539
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2540
     *                     we display a number of completed elements per total elements
2541
     * @param int    $add  Additional steps to fake as completed
2542
     *
2543
     * @return array Percentage or number and symbol (% or /xx)
2544
     */
2545
    public function get_progress_bar_text($mode = '', $add = 0)
2546
    {
2547
        if (empty($mode)) {
2548
            $mode = $this->progress_bar_mode;
2549
        }
2550
        $text = '';
2551
        $percentage = 0;
2552
        // If the option to use the score as progress is set for this learning
2553
        // path, then the rules are completely different: we assume only one
2554
        // item exists and the progress of the LP depends on the score
2555
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2556
        if ($scoreAsProgressSetting === true) {
2557
            $scoreAsProgress = $this->getUseScoreAsProgress();
2558
            if ($scoreAsProgress) {
2559
                // Get single item's score
2560
                $itemId = $this->get_current_item_id();
2561
                $item = $this->getItem($itemId);
2562
                $score = $item->get_score();
2563
                $maxScore = $item->get_max();
2564
                if ($mode = '%') {
2565
                    if (!empty($maxScore)) {
2566
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2567
                    }
2568
                    $percentage = number_format($percentage, 0);
2569
                    $text = '%';
2570
                } else {
2571
                    $percentage = $score;
2572
                    $text = '/'.$maxScore;
2573
                }
2574
2575
                return [$percentage, $text];
2576
            }
2577
        }
2578
        // otherwise just continue the normal processing of progress
2579
        $total_items = $this->getTotalItemsCountWithoutDirs();
2580
        $completeItems = $this->get_complete_items_count();
2581
        if ($add != 0) {
2582
            $completeItems += $add;
2583
        }
2584
        if ($completeItems > $total_items) {
2585
            $completeItems = $total_items;
2586
        }
2587
        if ($mode == '%') {
2588
            if ($total_items > 0) {
2589
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2590
            }
2591
            $percentage = number_format($percentage, 0);
2592
            $text = '%';
2593
        } elseif ($mode === 'abs') {
2594
            $percentage = $completeItems;
2595
            $text = '/'.$total_items;
2596
        }
2597
2598
        return [
2599
            $percentage,
2600
            $text,
2601
        ];
2602
    }
2603
2604
    /**
2605
     * Gets the progress bar mode.
2606
     *
2607
     * @return string The progress bar mode attribute
2608
     */
2609
    public function get_progress_bar_mode()
2610
    {
2611
        if (!empty($this->progress_bar_mode)) {
2612
            return $this->progress_bar_mode;
2613
        }
2614
2615
        return '%';
2616
    }
2617
2618
    /**
2619
     * Gets the learnpath theme (remote or local).
2620
     *
2621
     * @return string Learnpath theme
2622
     */
2623
    public function get_theme()
2624
    {
2625
        if (!empty($this->theme)) {
2626
            return $this->theme;
2627
        }
2628
2629
        return '';
2630
    }
2631
2632
    /**
2633
     * Gets the learnpath session id.
2634
     *
2635
     * @return int
2636
     */
2637
    public function get_lp_session_id()
2638
    {
2639
        if (!empty($this->lp_session_id)) {
2640
            return (int) $this->lp_session_id;
2641
        }
2642
2643
        return 0;
2644
    }
2645
2646
    /**
2647
     * Gets the learnpath image.
2648
     *
2649
     * @return string Web URL of the LP image
2650
     */
2651
    public function get_preview_image()
2652
    {
2653
        if (!empty($this->preview_image)) {
2654
            return $this->preview_image;
2655
        }
2656
2657
        return '';
2658
    }
2659
2660
    /**
2661
     * @param string $size
2662
     * @param string $path_type
2663
     *
2664
     * @return bool|string
2665
     */
2666
    public function get_preview_image_path($size = null, $path_type = 'web')
2667
    {
2668
        $preview_image = $this->get_preview_image();
2669
        if (isset($preview_image) && !empty($preview_image)) {
2670
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2671
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2672
2673
            if (isset($size)) {
2674
                $info = pathinfo($preview_image);
2675
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2676
2677
                if (file_exists($image_sys_path.$image_custom_size)) {
2678
                    if ($path_type == 'web') {
2679
                        return $image_path.$image_custom_size;
2680
                    } else {
2681
                        return $image_sys_path.$image_custom_size;
2682
                    }
2683
                }
2684
            } else {
2685
                if ($path_type == 'web') {
2686
                    return $image_path.$preview_image;
2687
                } else {
2688
                    return $image_sys_path.$preview_image;
2689
                }
2690
            }
2691
        }
2692
2693
        return false;
2694
    }
2695
2696
    /**
2697
     * Gets the learnpath author.
2698
     *
2699
     * @return string LP's author
2700
     */
2701
    public function get_author()
2702
    {
2703
        if (!empty($this->author)) {
2704
            return $this->author;
2705
        }
2706
2707
        return '';
2708
    }
2709
2710
    /**
2711
     * Gets hide table of contents.
2712
     *
2713
     * @return int
2714
     */
2715
    public function getHideTableOfContents()
2716
    {
2717
        return (int) $this->hide_toc_frame;
2718
    }
2719
2720
    /**
2721
     * Generate a new prerequisites string for a given item. If this item was a sco and
2722
     * its prerequisites were strings (instead of IDs), then transform those strings into
2723
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2724
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2725
     * same rule as the scormExport() method.
2726
     *
2727
     * @param int $item_id Item ID
2728
     *
2729
     * @return string Prerequisites string ready for the export as SCORM
2730
     */
2731
    public function get_scorm_prereq_string($item_id)
2732
    {
2733
        if ($this->debug > 0) {
2734
            error_log('In learnpath::get_scorm_prereq_string()');
2735
        }
2736
        if (!is_object($this->items[$item_id])) {
2737
            return false;
2738
        }
2739
        /** @var learnpathItem $oItem */
2740
        $oItem = $this->items[$item_id];
2741
        $prereq = $oItem->get_prereq_string();
2742
2743
        if (empty($prereq)) {
2744
            return '';
2745
        }
2746
        if (preg_match('/^\d+$/', $prereq) &&
2747
            isset($this->items[$prereq]) &&
2748
            is_object($this->items[$prereq])
2749
        ) {
2750
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2751
            // then simply return it (with the ITEM_ prefix).
2752
            //return 'ITEM_' . $prereq;
2753
            return $this->items[$prereq]->ref;
2754
        } else {
2755
            if (isset($this->refs_list[$prereq])) {
2756
                // It's a simple string item from which the ID can be found in the refs list,
2757
                // so we can transform it directly to an ID for export.
2758
                return $this->items[$this->refs_list[$prereq]]->ref;
2759
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2760
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2761
            } else {
2762
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2763
                // and replace them, one by one, by the internal IDs (chamilo db)
2764
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2765
                // by a space as well.
2766
                $find = [
2767
                    '&',
2768
                    '|',
2769
                    '~',
2770
                    '=',
2771
                    '<>',
2772
                    '{',
2773
                    '}',
2774
                    '*',
2775
                    '(',
2776
                    ')',
2777
                ];
2778
                $replace = [
2779
                    ' ',
2780
                    ' ',
2781
                    ' ',
2782
                    ' ',
2783
                    ' ',
2784
                    ' ',
2785
                    ' ',
2786
                    ' ',
2787
                    ' ',
2788
                    ' ',
2789
                ];
2790
                $prereq_mod = str_replace($find, $replace, $prereq);
2791
                $ids = explode(' ', $prereq_mod);
2792
                foreach ($ids as $id) {
2793
                    $id = trim($id);
2794
                    if (isset($this->refs_list[$id])) {
2795
                        $prereq = preg_replace(
2796
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2797
                            'ITEM_'.$this->refs_list[$id],
2798
                            $prereq
2799
                        );
2800
                    }
2801
                }
2802
2803
                return $prereq;
2804
            }
2805
        }
2806
    }
2807
2808
    /**
2809
     * Returns the XML DOM document's node.
2810
     *
2811
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2812
     * @param string   $id       The identifier to look for
2813
     *
2814
     * @return mixed The reference to the element found with that identifier. False if not found
2815
     */
2816
    public function get_scorm_xml_node(&$children, $id)
2817
    {
2818
        for ($i = 0; $i < $children->length; $i++) {
2819
            $item_temp = $children->item($i);
2820
            if ($item_temp->nodeName == 'item') {
2821
                if ($item_temp->getAttribute('identifier') == $id) {
2822
                    return $item_temp;
2823
                }
2824
            }
2825
            $subchildren = $item_temp->childNodes;
2826
            if ($subchildren && $subchildren->length > 0) {
2827
                $val = $this->get_scorm_xml_node($subchildren, $id);
2828
                if (is_object($val)) {
2829
                    return $val;
2830
                }
2831
            }
2832
        }
2833
2834
        return false;
2835
    }
2836
2837
    /**
2838
     * Gets the status list for all LP's items.
2839
     *
2840
     * @return array Array of [index] => [item ID => current status]
2841
     */
2842
    public function get_items_status_list()
2843
    {
2844
        $list = [];
2845
        foreach ($this->ordered_items as $item_id) {
2846
            $list[] = [
2847
                $item_id => $this->items[$item_id]->get_status(),
2848
            ];
2849
        }
2850
2851
        return $list;
2852
    }
2853
2854
    /**
2855
     * Return the number of interactions for the given learnpath Item View ID.
2856
     * This method can be used as static.
2857
     *
2858
     * @param int $lp_iv_id  Item View ID
2859
     * @param int $course_id course id
2860
     *
2861
     * @return int
2862
     */
2863
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2864
    {
2865
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2866
        $lp_iv_id = (int) $lp_iv_id;
2867
        $course_id = (int) $course_id;
2868
2869
        $sql = "SELECT count(*) FROM $table
2870
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2871
        $res = Database::query($sql);
2872
        $num = 0;
2873
        if (Database::num_rows($res)) {
2874
            $row = Database::fetch_array($res);
2875
            $num = $row[0];
2876
        }
2877
2878
        return $num;
2879
    }
2880
2881
    /**
2882
     * Return the interactions as an array for the given lp_iv_id.
2883
     * This method can be used as static.
2884
     *
2885
     * @param int $lp_iv_id Learnpath Item View ID
2886
     *
2887
     * @return array
2888
     *
2889
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2890
     */
2891
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2892
    {
2893
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2894
        $list = [];
2895
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2896
        $lp_iv_id = (int) $lp_iv_id;
2897
2898
        if (empty($lp_iv_id) || empty($course_id)) {
2899
            return [];
2900
        }
2901
2902
        $sql = "SELECT * FROM $table
2903
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2904
                ORDER BY order_id ASC";
2905
        $res = Database::query($sql);
2906
        $num = Database::num_rows($res);
2907
        if ($num > 0) {
2908
            $list[] = [
2909
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2910
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2911
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2912
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2913
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2914
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2915
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2916
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2917
                'student_response_formatted' => '',
2918
            ];
2919
            while ($row = Database::fetch_array($res)) {
2920
                $studentResponseFormatted = urldecode($row['student_response']);
2921
                $content_student_response = explode('__|', $studentResponseFormatted);
2922
                if (count($content_student_response) > 0) {
2923
                    if (count($content_student_response) >= 3) {
2924
                        // Pop the element off the end of array.
2925
                        array_pop($content_student_response);
2926
                    }
2927
                    $studentResponseFormatted = implode(',', $content_student_response);
2928
                }
2929
2930
                $list[] = [
2931
                    'order_id' => $row['order_id'] + 1,
2932
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2933
                    'type' => $row['interaction_type'],
2934
                    'time' => $row['completion_time'],
2935
                    'correct_responses' => '', // Hide correct responses from students.
2936
                    'student_response' => $row['student_response'],
2937
                    'result' => $row['result'],
2938
                    'latency' => $row['latency'],
2939
                    'student_response_formatted' => $studentResponseFormatted,
2940
                ];
2941
            }
2942
        }
2943
2944
        return $list;
2945
    }
2946
2947
    /**
2948
     * Return the number of objectives for the given learnpath Item View ID.
2949
     * This method can be used as static.
2950
     *
2951
     * @param int $lp_iv_id  Item View ID
2952
     * @param int $course_id Course ID
2953
     *
2954
     * @return int Number of objectives
2955
     */
2956
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2957
    {
2958
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2959
        $course_id = (int) $course_id;
2960
        $lp_iv_id = (int) $lp_iv_id;
2961
        $sql = "SELECT count(*) FROM $table
2962
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2963
        //@todo seems that this always returns 0
2964
        $res = Database::query($sql);
2965
        $num = 0;
2966
        if (Database::num_rows($res)) {
2967
            $row = Database::fetch_array($res);
2968
            $num = $row[0];
2969
        }
2970
2971
        return $num;
2972
    }
2973
2974
    /**
2975
     * Return the objectives as an array for the given lp_iv_id.
2976
     * This method can be used as static.
2977
     *
2978
     * @param int $lpItemViewId Learnpath Item View ID
2979
     * @param int $course_id
2980
     *
2981
     * @return array
2982
     *
2983
     * @todo    Translate labels
2984
     */
2985
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2986
    {
2987
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2988
        $lpItemViewId = (int) $lpItemViewId;
2989
2990
        if (empty($course_id) || empty($lpItemViewId)) {
2991
            return [];
2992
        }
2993
2994
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2995
        $sql = "SELECT * FROM $table
2996
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2997
                ORDER BY order_id ASC";
2998
        $res = Database::query($sql);
2999
        $num = Database::num_rows($res);
3000
        $list = [];
3001
        if ($num > 0) {
3002
            $list[] = [
3003
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3004
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3005
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3006
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3007
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3008
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3009
            ];
3010
            while ($row = Database::fetch_array($res)) {
3011
                $list[] = [
3012
                    'order_id' => $row['order_id'] + 1,
3013
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3014
                    'score_raw' => $row['score_raw'],
3015
                    'score_max' => $row['score_max'],
3016
                    'score_min' => $row['score_min'],
3017
                    'status' => $row['status'],
3018
                ];
3019
            }
3020
        }
3021
3022
        return $list;
3023
    }
3024
3025
    /**
3026
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3027
     * used by get_html_toc() to be ready to display.
3028
     *
3029
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3030
     */
3031
    public function get_toc()
3032
    {
3033
        $toc = [];
3034
        foreach ($this->ordered_items as $item_id) {
3035
            // TODO: Change this link generation and use new function instead.
3036
            $toc[] = [
3037
                'id' => $item_id,
3038
                'title' => $this->items[$item_id]->get_title(),
3039
                'status' => $this->items[$item_id]->get_status(),
3040
                'level' => $this->items[$item_id]->get_level(),
3041
                'type' => $this->items[$item_id]->get_type(),
3042
                'description' => $this->items[$item_id]->get_description(),
3043
                'path' => $this->items[$item_id]->get_path(),
3044
                'parent' => $this->items[$item_id]->get_parent(),
3045
            ];
3046
        }
3047
3048
        return $toc;
3049
    }
3050
3051
    /**
3052
     * Returns the CSS class name associated with a given item status.
3053
     *
3054
     * @param $status string an item status
3055
     *
3056
     * @return string CSS class name
3057
     */
3058
    public static function getStatusCSSClassName($status)
3059
    {
3060
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3061
            return self::STATUS_CSS_CLASS_NAME[$status];
3062
        }
3063
3064
        return '';
3065
    }
3066
3067
    /**
3068
     * Generate the tree of contents for this learnpath as an associative array tree
3069
     * with keys id, title, status, type, description, path, parent_id, children
3070
     * (title and descriptions as secured)
3071
     * and clues for CSS class composition:
3072
     *  - booleans is_current, is_parent_of_current, is_chapter
3073
     *  - string status_css_class_name.
3074
     *
3075
     * @param $parentId int restrict returned list to children of this parent
3076
     *
3077
     * @return array TOC as a table
3078
     */
3079
    public function getTOCTree($parentId = 0)
3080
    {
3081
        $toc = [];
3082
        $currentItemId = $this->get_current_item_id();
3083
3084
        foreach ($this->ordered_items as $itemId) {
3085
            $item = $this->items[$itemId];
3086
            if ($item->get_parent() == $parentId) {
3087
                $title = $item->get_title();
3088
                if (empty($title)) {
3089
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3090
                }
3091
3092
                $itemData = [
3093
                    'id' => $itemId,
3094
                    'title' => Security::remove_XSS($title),
3095
                    'status' => $item->get_status(),
3096
                    'level' => $item->get_level(), // FIXME should not be needed
3097
                    'type' => $item->get_type(),
3098
                    'description' => Security::remove_XSS($item->get_description()),
3099
                    'path' => $item->get_path(),
3100
                    'parent_id' => $item->get_parent(),
3101
                    'children' => $this->getTOCTree($itemId),
3102
                    'is_current' => ($itemId == $currentItemId),
3103
                    'is_parent_of_current' => false,
3104
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3105
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3106
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3107
                ];
3108
3109
                if (!empty($itemData['children'])) {
3110
                    foreach ($itemData['children'] as $child) {
3111
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3112
                            $itemData['is_parent_of_current'] = true;
3113
                            break;
3114
                        }
3115
                    }
3116
                }
3117
3118
                $toc[] = $itemData;
3119
            }
3120
        }
3121
3122
        return $toc;
3123
    }
3124
3125
    /**
3126
     * Generate and return the table of contents for this learnpath. The JS
3127
     * table returned is used inside of scorm_api.php.
3128
     *
3129
     * @param string $varname
3130
     *
3131
     * @return string A JS array variable construction
3132
     */
3133
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3134
    {
3135
        $toc = $varname.' = new Array();';
3136
        foreach ($this->ordered_items as $item_id) {
3137
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3138
        }
3139
3140
        return $toc;
3141
    }
3142
3143
    /**
3144
     * Gets the learning path type.
3145
     *
3146
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3147
     *
3148
     * @return mixed Type ID or name, depending on the parameter
3149
     */
3150
    public function get_type($get_name = false)
3151
    {
3152
        $res = false;
3153
        if (!empty($this->type) && (!$get_name)) {
3154
            $res = $this->type;
3155
        }
3156
3157
        return $res;
3158
    }
3159
3160
    /**
3161
     * Gets the learning path type as static method.
3162
     *
3163
     * @param int $lp_id
3164
     *
3165
     * @return mixed Type ID or name, depending on the parameter
3166
     */
3167
    public static function get_type_static($lp_id = 0)
3168
    {
3169
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3170
        $lp_id = (int) $lp_id;
3171
        $sql = "SELECT lp_type FROM $tbl_lp
3172
                WHERE iid = $lp_id";
3173
        $res = Database::query($sql);
3174
        if ($res === false) {
3175
            return null;
3176
        }
3177
        if (Database::num_rows($res) <= 0) {
3178
            return null;
3179
        }
3180
        $row = Database::fetch_array($res);
3181
3182
        return $row['lp_type'];
3183
    }
3184
3185
    /**
3186
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3187
     * This method can be used as abstract and is recursive.
3188
     *
3189
     * @param int $lp        Learnpath ID
3190
     * @param int $parent    Parent ID of the items to look for
3191
     * @param int $course_id
3192
     *
3193
     * @return array Ordered list of item IDs (empty array on error)
3194
     */
3195
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3196
    {
3197
        if (empty($course_id)) {
3198
            $course_id = api_get_course_int_id();
3199
        } else {
3200
            $course_id = (int) $course_id;
3201
        }
3202
        $list = [];
3203
3204
        if (empty($lp)) {
3205
            return $list;
3206
        }
3207
3208
        $lp = (int) $lp;
3209
        $parent = (int) $parent;
3210
3211
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3212
        $sql = "SELECT iid FROM $tbl_lp_item
3213
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3214
                ORDER BY display_order";
3215
3216
        $res = Database::query($sql);
3217
        while ($row = Database::fetch_array($res)) {
3218
            $sublist = self::get_flat_ordered_items_list(
3219
                $lp,
3220
                $row['iid'],
3221
                $course_id
3222
            );
3223
            $list[] = $row['iid'];
3224
            foreach ($sublist as $item) {
3225
                $list[] = $item;
3226
            }
3227
        }
3228
3229
        return $list;
3230
    }
3231
3232
    /**
3233
     * @return array
3234
     */
3235
    public static function getChapterTypes()
3236
    {
3237
        return [
3238
            'dir',
3239
        ];
3240
    }
3241
3242
    /**
3243
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3244
     *
3245
     * @param $tree
3246
     *
3247
     * @return array HTML TOC ready to display
3248
     */
3249
    public function getParentToc($tree)
3250
    {
3251
        if (empty($tree)) {
3252
            $tree = $this->get_toc();
3253
        }
3254
        $dirTypes = self::getChapterTypes();
3255
        $myCurrentId = $this->get_current_item_id();
3256
        $listParent = [];
3257
        $listChildren = [];
3258
        $listNotParent = [];
3259
        $list = [];
3260
        foreach ($tree as $subtree) {
3261
            if (in_array($subtree['type'], $dirTypes)) {
3262
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3263
                $subtree['children'] = $listChildren;
3264
                if (!empty($subtree['children'])) {
3265
                    foreach ($subtree['children'] as $subItem) {
3266
                        if ($subItem['id'] == $this->current) {
3267
                            $subtree['parent_current'] = 'in';
3268
                            $subtree['current'] = 'on';
3269
                        }
3270
                    }
3271
                }
3272
                $listParent[] = $subtree;
3273
            }
3274
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3275
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3276
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3277
                }
3278
3279
                $title = Security::remove_XSS($subtree['title']);
3280
                unset($subtree['title']);
3281
3282
                if (empty($title)) {
3283
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3284
                }
3285
                $classStyle = null;
3286
                if ($subtree['id'] == $this->current) {
3287
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3288
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3289
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3290
                }
3291
                $subtree['title'] = $title;
3292
                $subtree['class'] = $classStyle.' '.$cssStatus;
3293
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3294
                $subtree['current_id'] = $myCurrentId;
3295
                $listNotParent[] = $subtree;
3296
            }
3297
        }
3298
3299
        $list['are_parents'] = $listParent;
3300
        $list['not_parents'] = $listNotParent;
3301
3302
        return $list;
3303
    }
3304
3305
    /**
3306
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3307
     *
3308
     * @param array $tree
3309
     * @param int   $id
3310
     * @param bool  $parent
3311
     *
3312
     * @return array HTML TOC ready to display
3313
     */
3314
    public function getChildrenToc($tree, $id, $parent = true)
3315
    {
3316
        if (empty($tree)) {
3317
            $tree = $this->get_toc();
3318
        }
3319
3320
        $dirTypes = self::getChapterTypes();
3321
        $currentItemId = $this->get_current_item_id();
3322
        $list = [];
3323
3324
        foreach ($tree as $subtree) {
3325
            $subtree['tree'] = null;
3326
3327
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3328
                if ($subtree['id'] == $this->current) {
3329
                    $subtree['current'] = 'active';
3330
                } else {
3331
                    $subtree['current'] = null;
3332
                }
3333
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3334
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3335
                }
3336
3337
                $title = Security::remove_XSS($subtree['title']);
3338
                unset($subtree['title']);
3339
                if (empty($title)) {
3340
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3341
                }
3342
3343
                $classStyle = null;
3344
                if ($subtree['id'] == $this->current) {
3345
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3346
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3347
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3348
                }
3349
3350
                if (in_array($subtree['type'], $dirTypes)) {
3351
                    $subtree['title'] = stripslashes($title);
3352
                } else {
3353
                    $subtree['title'] = $title;
3354
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3355
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3356
                    $subtree['current_id'] = $currentItemId;
3357
                }
3358
                $list[] = $subtree;
3359
            }
3360
        }
3361
3362
        return $list;
3363
    }
3364
3365
    /**
3366
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3367
     *
3368
     * @param array $toc_list
3369
     *
3370
     * @return array HTML TOC ready to display
3371
     */
3372
    public function getListArrayToc($toc_list = [])
3373
    {
3374
        if (empty($toc_list)) {
3375
            $toc_list = $this->get_toc();
3376
        }
3377
        // Temporary variables.
3378
        $currentItemId = $this->get_current_item_id();
3379
        $list = [];
3380
        $arrayList = [];
3381
3382
        foreach ($toc_list as $item) {
3383
            $list['id'] = $item['id'];
3384
            $list['status'] = $item['status'];
3385
            $cssStatus = null;
3386
3387
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3388
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3389
            }
3390
3391
            $classStyle = ' ';
3392
            $dirTypes = self::getChapterTypes();
3393
3394
            if (in_array($item['type'], $dirTypes)) {
3395
                $classStyle = 'scorm_item_section ';
3396
            }
3397
            if ($item['id'] == $this->current) {
3398
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3399
            } elseif (!in_array($item['type'], $dirTypes)) {
3400
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3401
            }
3402
            $title = $item['title'];
3403
            if (empty($title)) {
3404
                $title = self::rl_get_resource_name(
3405
                    api_get_course_id(),
3406
                    $this->get_id(),
3407
                    $item['id']
3408
                );
3409
            }
3410
            $title = Security::remove_XSS($item['title']);
3411
3412
            if (empty($item['description'])) {
3413
                $list['description'] = $title;
3414
            } else {
3415
                $list['description'] = $item['description'];
3416
            }
3417
3418
            $list['class'] = $classStyle.' '.$cssStatus;
3419
            $list['level'] = $item['level'];
3420
            $list['type'] = $item['type'];
3421
3422
            if (in_array($item['type'], $dirTypes)) {
3423
                $list['css_level'] = 'level_'.$item['level'];
3424
            } else {
3425
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3426
            }
3427
3428
            if (in_array($item['type'], $dirTypes)) {
3429
                $list['title'] = stripslashes($title);
3430
            } else {
3431
                $list['title'] = stripslashes($title);
3432
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3433
                $list['current_id'] = $currentItemId;
3434
            }
3435
            $arrayList[] = $list;
3436
        }
3437
3438
        return $arrayList;
3439
    }
3440
3441
    /**
3442
     * Returns an HTML-formatted string ready to display with teacher buttons
3443
     * in LP view menu.
3444
     *
3445
     * @return string HTML TOC ready to display
3446
     */
3447
    public function get_teacher_toc_buttons()
3448
    {
3449
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3450
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3451
        $html = '';
3452
        if ($isAllow && $hideIcons == false) {
3453
            if ($this->get_lp_session_id() == api_get_session_id()) {
3454
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3455
                $html .= '<div class="btn-group">';
3456
                $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'>".
3457
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3458
                $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'>".
3459
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3460
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3461
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3462
                $html .= '</div>';
3463
                $html .= '</div>';
3464
            }
3465
        }
3466
3467
        return $html;
3468
    }
3469
3470
    /**
3471
     * Gets the learnpath maker name - generally the editor's name.
3472
     *
3473
     * @return string Learnpath maker name
3474
     */
3475
    public function get_maker()
3476
    {
3477
        if (!empty($this->maker)) {
3478
            return $this->maker;
3479
        }
3480
3481
        return '';
3482
    }
3483
3484
    /**
3485
     * Gets the learnpath name/title.
3486
     *
3487
     * @return string Learnpath name/title
3488
     */
3489
    public function get_name()
3490
    {
3491
        if (!empty($this->name)) {
3492
            return $this->name;
3493
        }
3494
3495
        return 'N/A';
3496
    }
3497
3498
    /**
3499
     * @return string
3500
     */
3501
    public function getNameNoTags()
3502
    {
3503
        return strip_tags($this->get_name());
3504
    }
3505
3506
    /**
3507
     * Gets a link to the resource from the present location, depending on item ID.
3508
     *
3509
     * @param string $type         Type of link expected
3510
     * @param int    $item_id      Learnpath item ID
3511
     * @param bool   $provided_toc
3512
     *
3513
     * @return string $provided_toc Link to the lp_item resource
3514
     */
3515
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3516
    {
3517
        $course_id = $this->get_course_int_id();
3518
        $item_id = (int) $item_id;
3519
3520
        if (empty($item_id)) {
3521
            $item_id = $this->get_current_item_id();
3522
3523
            if (empty($item_id)) {
3524
                //still empty, this means there was no item_id given and we are not in an object context or
3525
                //the object property is empty, return empty link
3526
                $this->first();
3527
3528
                return '';
3529
            }
3530
        }
3531
3532
        $file = '';
3533
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3534
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3535
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3536
3537
        $sql = "SELECT
3538
                    l.lp_type as ltype,
3539
                    l.path as lpath,
3540
                    li.item_type as litype,
3541
                    li.path as lipath,
3542
                    li.parameters as liparams
3543
        		FROM $lp_table l
3544
                INNER JOIN $lp_item_table li
3545
                ON (li.lp_id = l.iid)
3546
        		WHERE
3547
        		    li.iid = $item_id
3548
        		";
3549
        $res = Database::query($sql);
3550
        if (Database::num_rows($res) > 0) {
3551
            $row = Database::fetch_array($res);
3552
            $lp_type = $row['ltype'];
3553
            $lp_path = $row['lpath'];
3554
            $lp_item_type = $row['litype'];
3555
            $lp_item_path = $row['lipath'];
3556
            $lp_item_params = $row['liparams'];
3557
3558
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3559
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3560
            }
3561
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3562
            if ($type === 'http') {
3563
                //web path
3564
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3565
            } else {
3566
                $course_path = $sys_course_path; //system path
3567
            }
3568
3569
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3570
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3571
            if (in_array(
3572
                $lp_item_type,
3573
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3574
            )
3575
            ) {
3576
                $lp_type = 1;
3577
            }
3578
3579
            // Now go through the specific cases to get the end of the path
3580
            // @todo Use constants instead of int values.
3581
            switch ($lp_type) {
3582
                case 1:
3583
                    $file = self::rl_get_resource_link_for_learnpath(
3584
                        $course_id,
3585
                        $this->get_id(),
3586
                        $item_id,
3587
                        $this->get_view_id()
3588
                    );
3589
                    switch ($lp_item_type) {
3590
                        case 'document':
3591
                            // Shows a button to download the file instead of just downloading the file directly.
3592
                            $documentPathInfo = pathinfo($file);
3593
                            if (isset($documentPathInfo['extension'])) {
3594
                                $parsed = parse_url($documentPathInfo['extension']);
3595
                                if (isset($parsed['path'])) {
3596
                                    $extension = $parsed['path'];
3597
                                    $extensionsToDownload = [
3598
                                        'zip',
3599
                                        'ppt',
3600
                                        'pptx',
3601
                                        'ods',
3602
                                        'xlsx',
3603
                                        'xls',
3604
                                        'csv',
3605
                                        'doc',
3606
                                        'docx',
3607
                                        'dot',
3608
                                    ];
3609
3610
                                    if (in_array($extension, $extensionsToDownload)) {
3611
                                        $file = api_get_path(WEB_CODE_PATH).
3612
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3613
                                    }
3614
                                }
3615
                            }
3616
                            break;
3617
                        case 'dir':
3618
                            $file = 'lp_content.php?type=dir';
3619
                            break;
3620
                        case 'link':
3621
                            if (Link::is_youtube_link($file)) {
3622
                                $src = Link::get_youtube_video_id($file);
3623
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3624
                            } elseif (Link::isVimeoLink($file)) {
3625
                                $src = Link::getVimeoLinkId($file);
3626
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3627
                            } else {
3628
                                // If the current site is HTTPS and the link is
3629
                                // HTTP, browsers will refuse opening the link
3630
                                $urlId = api_get_current_access_url_id();
3631
                                $url = api_get_access_url($urlId, false);
3632
                                $protocol = substr($url['url'], 0, 5);
3633
                                if ($protocol === 'https') {
3634
                                    $linkProtocol = substr($file, 0, 5);
3635
                                    if ($linkProtocol === 'http:') {
3636
                                        //this is the special intervention case
3637
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3638
                                    }
3639
                                }
3640
                            }
3641
                            break;
3642
                        case 'quiz':
3643
                            // Check how much attempts of a exercise exits in lp
3644
                            $lp_item_id = $this->get_current_item_id();
3645
                            $lp_view_id = $this->get_view_id();
3646
3647
                            $prevent_reinit = null;
3648
                            if (isset($this->items[$this->current])) {
3649
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3650
                            }
3651
3652
                            if (empty($provided_toc)) {
3653
                                if ($this->debug > 0) {
3654
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3655
                                }
3656
                                $list = $this->get_toc();
3657
                            } else {
3658
                                if ($this->debug > 0) {
3659
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3660
                                }
3661
                                $list = $provided_toc;
3662
                            }
3663
3664
                            $type_quiz = false;
3665
                            foreach ($list as $toc) {
3666
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3667
                                    $type_quiz = true;
3668
                                }
3669
                            }
3670
3671
                            if ($type_quiz) {
3672
                                $lp_item_id = (int) $lp_item_id;
3673
                                $lp_view_id = (int) $lp_view_id;
3674
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3675
                                        WHERE
3676
                                            c_id = $course_id AND
3677
                                            lp_item_id='".$lp_item_id."' AND
3678
                                            lp_view_id ='".$lp_view_id."' AND
3679
                                            status='completed'";
3680
                                $result = Database::query($sql);
3681
                                $row_count = Database:: fetch_row($result);
3682
                                $count_item_view = (int) $row_count[0];
3683
                                $not_multiple_attempt = 0;
3684
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3685
                                    $not_multiple_attempt = 1;
3686
                                }
3687
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3688
                            }
3689
                            break;
3690
                    }
3691
3692
                    $tmp_array = explode('/', $file);
3693
                    $document_name = $tmp_array[count($tmp_array) - 1];
3694
                    if (strpos($document_name, '_DELETED_')) {
3695
                        $file = 'blank.php?error=document_deleted';
3696
                    }
3697
                    break;
3698
                case 2:
3699
                    if ($this->debug > 2) {
3700
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3701
                    }
3702
3703
                    if ($lp_item_type != 'dir') {
3704
                        // Quite complex here:
3705
                        // We want to make sure 'http://' (and similar) links can
3706
                        // be loaded as is (withouth the Chamilo path in front) but
3707
                        // some contents use this form: resource.htm?resource=http://blablabla
3708
                        // which means we have to find a protocol at the path's start, otherwise
3709
                        // it should not be considered as an external URL.
3710
                        // if ($this->prerequisites_match($item_id)) {
3711
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3712
                            if ($this->debug > 2) {
3713
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3714
                            }
3715
                            // Distant url, return as is.
3716
                            $file = $lp_item_path;
3717
                        } else {
3718
                            if ($this->debug > 2) {
3719
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3720
                            }
3721
                            // Prevent getting untranslatable urls.
3722
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3723
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3724
                            // Prepare the path.
3725
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3726
                            // TODO: Fix this for urls with protocol header.
3727
                            $file = str_replace('//', '/', $file);
3728
                            $file = str_replace(':/', '://', $file);
3729
                            if (substr($lp_path, -1) == '/') {
3730
                                $lp_path = substr($lp_path, 0, -1);
3731
                            }
3732
3733
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3734
                                // if file not found.
3735
                                $decoded = html_entity_decode($lp_item_path);
3736
                                list($decoded) = explode('?', $decoded);
3737
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3738
                                    $file = self::rl_get_resource_link_for_learnpath(
3739
                                        $course_id,
3740
                                        $this->get_id(),
3741
                                        $item_id,
3742
                                        $this->get_view_id()
3743
                                    );
3744
                                    if (empty($file)) {
3745
                                        $file = 'blank.php?error=document_not_found';
3746
                                    } else {
3747
                                        $tmp_array = explode('/', $file);
3748
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3749
                                        if (strpos($document_name, '_DELETED_')) {
3750
                                            $file = 'blank.php?error=document_deleted';
3751
                                        } else {
3752
                                            $file = 'blank.php?error=document_not_found';
3753
                                        }
3754
                                    }
3755
                                } else {
3756
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3757
                                }
3758
                            }
3759
                        }
3760
3761
                        // We want to use parameters if they were defined in the imsmanifest
3762
                        if (strpos($file, 'blank.php') === false) {
3763
                            $lp_item_params = ltrim($lp_item_params, '?');
3764
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3765
                        }
3766
                    } else {
3767
                        $file = 'lp_content.php?type=dir';
3768
                    }
3769
                    break;
3770
                case 3:
3771
                    if ($this->debug > 2) {
3772
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3773
                    }
3774
                    // Formatting AICC HACP append URL.
3775
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3776
                    if (!empty($lp_item_params)) {
3777
                        $aicc_append .= $lp_item_params.'&';
3778
                    }
3779
                    if ($lp_item_type != 'dir') {
3780
                        // Quite complex here:
3781
                        // We want to make sure 'http://' (and similar) links can
3782
                        // be loaded as is (withouth the Chamilo path in front) but
3783
                        // some contents use this form: resource.htm?resource=http://blablabla
3784
                        // which means we have to find a protocol at the path's start, otherwise
3785
                        // it should not be considered as an external URL.
3786
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3787
                            if ($this->debug > 2) {
3788
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3789
                            }
3790
                            // Distant url, return as is.
3791
                            $file = $lp_item_path;
3792
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3793
                            /*
3794
                            if (stristr($file,'<servername>') !== false) {
3795
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3796
                            }
3797
                            */
3798
                            if (stripos($file, '<servername>') !== false) {
3799
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3800
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3801
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3802
                            }
3803
3804
                            $file .= $aicc_append;
3805
                        } else {
3806
                            if ($this->debug > 2) {
3807
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3808
                            }
3809
                            // Prevent getting untranslatable urls.
3810
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3811
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3812
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3813
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3814
                            // TODO: Fix this for urls with protocol header.
3815
                            $file = str_replace('//', '/', $file);
3816
                            $file = str_replace(':/', '://', $file);
3817
                            $file .= $aicc_append;
3818
                        }
3819
                    } else {
3820
                        $file = 'lp_content.php?type=dir';
3821
                    }
3822
                    break;
3823
                case 4:
3824
                    break;
3825
                default:
3826
                    break;
3827
            }
3828
            // Replace &amp; by & because &amp; will break URL with params
3829
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3830
        }
3831
        if ($this->debug > 2) {
3832
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3833
        }
3834
3835
        return $file;
3836
    }
3837
3838
    /**
3839
     * Gets the latest usable view or generate a new one.
3840
     *
3841
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3842
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3843
     *
3844
     * @return int DB lp_view id
3845
     */
3846
    public function get_view($attempt_num = 0, $userId = null)
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
        // Check user ID.
3860
        if (empty($userId)) {
3861
            if (empty($this->get_user_id())) {
3862
                $this->error = 'User ID is empty in learnpath::get_view()';
3863
3864
                return null;
3865
            } else {
3866
                $userId = $this->get_user_id();
3867
            }
3868
        }
3869
3870
        $sql = "SELECT iid, view_count FROM $lp_view_table
3871
        		WHERE
3872
        		    c_id = $course_id AND
3873
        		    lp_id = ".$this->get_id()." AND
3874
        		    user_id = ".$userId." AND
3875
        		    session_id = $sessionId
3876
        		    $search
3877
                ORDER BY view_count DESC";
3878
        $res = Database::query($sql);
3879
        if (Database::num_rows($res) > 0) {
3880
            $row = Database::fetch_array($res);
3881
            $this->lp_view_id = $row['iid'];
3882
        } elseif (!api_is_invitee()) {
3883
            // There is no database record, create one.
3884
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3885
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3886
            Database::query($sql);
3887
            $id = Database::insert_id();
3888
            $this->lp_view_id = $id;
3889
3890
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3891
            Database::query($sql);
3892
        }
3893
3894
        return $this->lp_view_id;
3895
    }
3896
3897
    /**
3898
     * Gets the current view id.
3899
     *
3900
     * @return int View ID (from lp_view)
3901
     */
3902
    public function get_view_id()
3903
    {
3904
        if (!empty($this->lp_view_id)) {
3905
            return (int) $this->lp_view_id;
3906
        }
3907
3908
        return 0;
3909
    }
3910
3911
    /**
3912
     * Gets the update queue.
3913
     *
3914
     * @return array Array containing IDs of items to be updated by JavaScript
3915
     */
3916
    public function get_update_queue()
3917
    {
3918
        return $this->update_queue;
3919
    }
3920
3921
    /**
3922
     * Gets the user ID.
3923
     *
3924
     * @return int User ID
3925
     */
3926
    public function get_user_id()
3927
    {
3928
        if (!empty($this->user_id)) {
3929
            return (int) $this->user_id;
3930
        }
3931
3932
        return false;
3933
    }
3934
3935
    /**
3936
     * Checks if any of the items has an audio element attached.
3937
     *
3938
     * @return bool True or false
3939
     */
3940
    public function has_audio()
3941
    {
3942
        $has = false;
3943
        foreach ($this->items as $i => $item) {
3944
            if (!empty($this->items[$i]->audio)) {
3945
                $has = true;
3946
                break;
3947
            }
3948
        }
3949
3950
        return $has;
3951
    }
3952
3953
    /**
3954
     * Moves an item up and down at its level.
3955
     *
3956
     * @param int    $id        Item to move up and down
3957
     * @param string $direction Direction 'up' or 'down'
3958
     *
3959
     * @return bool|int
3960
     */
3961
    public function move_item($id, $direction)
3962
    {
3963
        $course_id = api_get_course_int_id();
3964
        if (empty($id) || empty($direction)) {
3965
            return false;
3966
        }
3967
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3968
        $sql_sel = "SELECT *
3969
                    FROM $tbl_lp_item
3970
                    WHERE
3971
                        iid = $id
3972
                    ";
3973
        $res_sel = Database::query($sql_sel);
3974
        // Check if elem exists.
3975
        if (Database::num_rows($res_sel) < 1) {
3976
            return false;
3977
        }
3978
        // Gather data.
3979
        $row = Database::fetch_array($res_sel);
3980
        $previous = $row['previous_item_id'];
3981
        $next = $row['next_item_id'];
3982
        $display = $row['display_order'];
3983
        $parent = $row['parent_item_id'];
3984
        $lp = $row['lp_id'];
3985
        // Update the item (switch with previous/next one).
3986
        switch ($direction) {
3987
            case 'up':
3988
                if ($display > 1) {
3989
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3990
                                 WHERE iid = $previous";
3991
                    $res_sel2 = Database::query($sql_sel2);
3992
                    if (Database::num_rows($res_sel2) < 1) {
3993
                        $previous_previous = 0;
3994
                    }
3995
                    // Gather data.
3996
                    $row2 = Database::fetch_array($res_sel2);
3997
                    $previous_previous = $row2['previous_item_id'];
3998
                    // Update previous_previous item (switch "next" with current).
3999
                    if ($previous_previous != 0) {
4000
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4001
                                        next_item_id = $id
4002
                                    WHERE iid = $previous_previous";
4003
                        Database::query($sql_upd2);
4004
                    }
4005
                    // Update previous item (switch with current).
4006
                    if ($previous != 0) {
4007
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4008
                                    next_item_id = $next,
4009
                                    previous_item_id = $id,
4010
                                    display_order = display_order +1
4011
                                    WHERE iid = $previous";
4012
                        Database::query($sql_upd2);
4013
                    }
4014
4015
                    // Update current item (switch with previous).
4016
                    if ($id != 0) {
4017
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4018
                                        next_item_id = $previous,
4019
                                        previous_item_id = $previous_previous,
4020
                                        display_order = display_order-1
4021
                                    WHERE c_id = ".$course_id." AND id = $id";
4022
                        Database::query($sql_upd2);
4023
                    }
4024
                    // Update next item (new previous item).
4025
                    if (!empty($next)) {
4026
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4027
                                     WHERE iid = $next";
4028
                        Database::query($sql_upd2);
4029
                    }
4030
                    $display = $display - 1;
4031
                }
4032
                break;
4033
            case 'down':
4034
                if ($next != 0) {
4035
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4036
                                 WHERE iid = $next";
4037
                    $res_sel2 = Database::query($sql_sel2);
4038
                    if (Database::num_rows($res_sel2) < 1) {
4039
                        $next_next = 0;
4040
                    }
4041
                    // Gather data.
4042
                    $row2 = Database::fetch_array($res_sel2);
4043
                    $next_next = $row2['next_item_id'];
4044
                    // Update previous item (switch with current).
4045
                    if ($previous != 0) {
4046
                        $sql_upd2 = "UPDATE $tbl_lp_item
4047
                                     SET next_item_id = $next
4048
                                     WHERE iid = $previous";
4049
                        Database::query($sql_upd2);
4050
                    }
4051
                    // Update current item (switch with previous).
4052
                    if ($id != 0) {
4053
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4054
                                     previous_item_id = $next,
4055
                                     next_item_id = $next_next,
4056
                                     display_order = display_order + 1
4057
                                     WHERE iid = $id";
4058
                        Database::query($sql_upd2);
4059
                    }
4060
4061
                    // Update next item (new previous item).
4062
                    if ($next != 0) {
4063
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4064
                                     previous_item_id = $previous,
4065
                                     next_item_id = $id,
4066
                                     display_order = display_order-1
4067
                                     WHERE iid = $next";
4068
                        Database::query($sql_upd2);
4069
                    }
4070
4071
                    // Update next_next item (switch "previous" with current).
4072
                    if ($next_next != 0) {
4073
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4074
                                     previous_item_id = $id
4075
                                     WHERE iid = $next_next";
4076
                        Database::query($sql_upd2);
4077
                    }
4078
                    $display = $display + 1;
4079
                }
4080
                break;
4081
            default:
4082
                return false;
4083
        }
4084
4085
        return $display;
4086
    }
4087
4088
    /**
4089
     * Move a LP up (display_order).
4090
     *
4091
     * @param int $lp_id      Learnpath ID
4092
     * @param int $categoryId Category ID
4093
     *
4094
     * @return bool
4095
     */
4096
    public static function move_up($lp_id, $categoryId = 0)
4097
    {
4098
        $courseId = api_get_course_int_id();
4099
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4100
4101
        $categoryCondition = '';
4102
        if (!empty($categoryId)) {
4103
            $categoryId = (int) $categoryId;
4104
            $categoryCondition = " AND category_id = $categoryId";
4105
        }
4106
        $sql = "SELECT * FROM $lp_table
4107
                WHERE c_id = $courseId
4108
                $categoryCondition
4109
                ORDER BY display_order";
4110
        $res = Database::query($sql);
4111
        if ($res === false) {
4112
            return false;
4113
        }
4114
4115
        $lps = [];
4116
        $lp_order = [];
4117
        $num = Database::num_rows($res);
4118
        // First check the order is correct, globally (might be wrong because
4119
        // of versions < 1.8.4)
4120
        if ($num > 0) {
4121
            $i = 1;
4122
            while ($row = Database::fetch_array($res)) {
4123
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4124
                    $sql = "UPDATE $lp_table SET display_order = $i
4125
                            WHERE iid = ".$row['iid'];
4126
                    Database::query($sql);
4127
                }
4128
                $row['display_order'] = $i;
4129
                $lps[$row['iid']] = $row;
4130
                $lp_order[$i] = $row['iid'];
4131
                $i++;
4132
            }
4133
        }
4134
        if ($num > 1) { // If there's only one element, no need to sort.
4135
            $order = $lps[$lp_id]['display_order'];
4136
            if ($order > 1) { // If it's the first element, no need to move up.
4137
                $sql = "UPDATE $lp_table SET display_order = $order
4138
                        WHERE iid = ".$lp_order[$order - 1];
4139
                Database::query($sql);
4140
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4141
                        WHERE iid = $lp_id";
4142
                Database::query($sql);
4143
            }
4144
        }
4145
4146
        return true;
4147
    }
4148
4149
    /**
4150
     * Move a learnpath down (display_order).
4151
     *
4152
     * @param int $lp_id      Learnpath ID
4153
     * @param int $categoryId Category ID
4154
     *
4155
     * @return bool
4156
     */
4157
    public static function move_down($lp_id, $categoryId = 0)
4158
    {
4159
        $courseId = api_get_course_int_id();
4160
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4161
4162
        $categoryCondition = '';
4163
        if (!empty($categoryId)) {
4164
            $categoryId = (int) $categoryId;
4165
            $categoryCondition = " AND category_id = $categoryId";
4166
        }
4167
4168
        $sql = "SELECT * FROM $lp_table
4169
                WHERE c_id = $courseId
4170
                $categoryCondition
4171
                ORDER BY display_order";
4172
        $res = Database::query($sql);
4173
        if ($res === false) {
4174
            return false;
4175
        }
4176
        $lps = [];
4177
        $lp_order = [];
4178
        $num = Database::num_rows($res);
4179
        $max = 0;
4180
        // First check the order is correct, globally (might be wrong because
4181
        // of versions < 1.8.4).
4182
        if ($num > 0) {
4183
            $i = 1;
4184
            while ($row = Database::fetch_array($res)) {
4185
                $max = $i;
4186
                if ($row['display_order'] != $i) {
4187
                    // If we find a gap in the order, we need to fix it.
4188
                    $sql = "UPDATE $lp_table SET display_order = $i
4189
                              WHERE iid = ".$row['iid'];
4190
                    Database::query($sql);
4191
                }
4192
                $row['display_order'] = $i;
4193
                $lps[$row['iid']] = $row;
4194
                $lp_order[$i] = $row['iid'];
4195
                $i++;
4196
            }
4197
        }
4198
        if ($num > 1) { // If there's only one element, no need to sort.
4199
            $order = $lps[$lp_id]['display_order'];
4200
            if ($order < $max) { // If it's the first element, no need to move up.
4201
                $sql = "UPDATE $lp_table SET display_order = $order
4202
                        WHERE iid = ".$lp_order[$order + 1];
4203
                Database::query($sql);
4204
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4205
                        WHERE iid = $lp_id";
4206
                Database::query($sql);
4207
            }
4208
        }
4209
4210
        return true;
4211
    }
4212
4213
    /**
4214
     * Updates learnpath attributes to point to the next element
4215
     * The last part is similar to set_current_item but processing the other way around.
4216
     */
4217
    public function next()
4218
    {
4219
        if ($this->debug > 0) {
4220
            error_log('In learnpath::next()', 0);
4221
        }
4222
        $this->last = $this->get_current_item_id();
4223
        $this->items[$this->last]->save(
4224
            false,
4225
            $this->prerequisites_match($this->last)
4226
        );
4227
        $this->autocomplete_parents($this->last);
4228
        $new_index = $this->get_next_index();
4229
        if ($this->debug > 2) {
4230
            error_log('New index: '.$new_index, 0);
4231
        }
4232
        $this->index = $new_index;
4233
        if ($this->debug > 2) {
4234
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4235
        }
4236
        $this->current = $this->ordered_items[$new_index];
4237
        if ($this->debug > 2) {
4238
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4239
        }
4240
    }
4241
4242
    /**
4243
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4244
     * class, this might be redefined to allow several behaviours depending on the document type.
4245
     *
4246
     * @param int $id Resource ID
4247
     */
4248
    public function open($id)
4249
    {
4250
        // TODO:
4251
        // set the current resource attribute to this resource
4252
        // switch on element type (redefine in child class?)
4253
        // set status for this item to "opened"
4254
        // start timer
4255
        // initialise score
4256
        $this->index = 0; //or = the last item seen (see $this->last)
4257
    }
4258
4259
    /**
4260
     * Check that all prerequisites are fulfilled. Returns true and an
4261
     * empty string on success, returns false
4262
     * and the prerequisite string on error.
4263
     * This function is based on the rules for aicc_script language as
4264
     * described in the SCORM 1.2 CAM documentation page 108.
4265
     *
4266
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4267
     *
4268
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4269
     *              string otherwise
4270
     */
4271
    public function prerequisites_match($itemId = null)
4272
    {
4273
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4274
        if ($allow) {
4275
            if (api_is_allowed_to_edit() ||
4276
                api_is_platform_admin(true) ||
4277
                api_is_drh() ||
4278
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4279
            ) {
4280
                return true;
4281
            }
4282
        }
4283
4284
        $debug = $this->debug;
4285
        if ($debug > 0) {
4286
            error_log('In learnpath::prerequisites_match()');
4287
        }
4288
4289
        if (empty($itemId)) {
4290
            $itemId = $this->current;
4291
        }
4292
4293
        $currentItem = $this->getItem($itemId);
4294
4295
        if ($currentItem) {
4296
            if ($this->type == 2) {
4297
                // Getting prereq from scorm
4298
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4299
            } else {
4300
                $prereq_string = $currentItem->get_prereq_string();
4301
            }
4302
4303
            if (empty($prereq_string)) {
4304
                if ($debug > 0) {
4305
                    error_log('Found prereq_string is empty return true');
4306
                }
4307
4308
                return true;
4309
            }
4310
4311
            // Clean spaces.
4312
            $prereq_string = str_replace(' ', '', $prereq_string);
4313
            if ($debug > 0) {
4314
                error_log('Found prereq_string: '.$prereq_string, 0);
4315
            }
4316
4317
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4318
            $result = $currentItem->parse_prereq(
4319
                $prereq_string,
4320
                $this->items,
4321
                $this->refs_list,
4322
                $this->get_user_id()
4323
            );
4324
4325
            if ($result === false) {
4326
                $this->set_error_msg($currentItem->prereq_alert);
4327
            }
4328
        } else {
4329
            $result = true;
4330
            if ($debug > 1) {
4331
                error_log('$this->items['.$itemId.'] was not an object', 0);
4332
            }
4333
        }
4334
4335
        if ($debug > 1) {
4336
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4337
        }
4338
4339
        return $result;
4340
    }
4341
4342
    /**
4343
     * Updates learnpath attributes to point to the previous element
4344
     * The last part is similar to set_current_item but processing the other way around.
4345
     */
4346
    public function previous()
4347
    {
4348
        $this->last = $this->get_current_item_id();
4349
        $this->items[$this->last]->save(
4350
            false,
4351
            $this->prerequisites_match($this->last)
4352
        );
4353
        $this->autocomplete_parents($this->last);
4354
        $new_index = $this->get_previous_index();
4355
        $this->index = $new_index;
4356
        $this->current = $this->ordered_items[$new_index];
4357
    }
4358
4359
    /**
4360
     * Publishes a learnpath. This basically means show or hide the learnpath
4361
     * to normal users.
4362
     * Can be used as abstract.
4363
     *
4364
     * @param int $lp_id          Learnpath ID
4365
     * @param int $set_visibility New visibility
4366
     *
4367
     * @return bool
4368
     */
4369
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4370
    {
4371
        $action = 'visible';
4372
        if ($set_visibility != 1) {
4373
            $action = 'invisible';
4374
            self::toggle_publish($lp_id, 'i');
4375
        }
4376
4377
        return api_item_property_update(
4378
            api_get_course_info(),
4379
            TOOL_LEARNPATH,
4380
            $lp_id,
4381
            $action,
4382
            api_get_user_id()
4383
        );
4384
    }
4385
4386
    /**
4387
     * Publishes a learnpath category.
4388
     * This basically means show or hide the learnpath category to normal users.
4389
     *
4390
     * @param int $id
4391
     * @param int $visibility
4392
     *
4393
     * @throws \Doctrine\ORM\NonUniqueResultException
4394
     * @throws \Doctrine\ORM\ORMException
4395
     * @throws \Doctrine\ORM\OptimisticLockException
4396
     * @throws \Doctrine\ORM\TransactionRequiredException
4397
     *
4398
     * @return bool
4399
     */
4400
    public static function toggleCategoryVisibility($id, $visibility = 1)
4401
    {
4402
        $action = 'visible';
4403
        if ($visibility != 1) {
4404
            self::toggleCategoryPublish($id, 0);
4405
            $action = 'invisible';
4406
        }
4407
4408
        return api_item_property_update(
4409
            api_get_course_info(),
4410
            TOOL_LEARNPATH_CATEGORY,
4411
            $id,
4412
            $action,
4413
            api_get_user_id()
4414
        );
4415
    }
4416
4417
    /**
4418
     * Publishes a learnpath. This basically means show or hide the learnpath
4419
     * on the course homepage
4420
     * Can be used as abstract.
4421
     *
4422
     * @param int    $lp_id          Learnpath id
4423
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4424
     *
4425
     * @return bool
4426
     */
4427
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4428
    {
4429
        $course_id = api_get_course_int_id();
4430
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4431
        $lp_id = (int) $lp_id;
4432
        $sql = "SELECT * FROM $tbl_lp
4433
                WHERE iid = $lp_id";
4434
        $result = Database::query($sql);
4435
        if (Database::num_rows($result)) {
4436
            $row = Database::fetch_array($result);
4437
            $name = Database::escape_string($row['name']);
4438
            if ($set_visibility == 'i') {
4439
                $v = 0;
4440
            }
4441
            if ($set_visibility == 'v') {
4442
                $v = 1;
4443
            }
4444
4445
            $session_id = api_get_session_id();
4446
            $session_condition = api_get_session_condition($session_id);
4447
4448
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4449
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4450
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4451
4452
            $sql = "SELECT * FROM $tbl_tool
4453
                    WHERE
4454
                        c_id = $course_id AND
4455
                        (link = '$link' OR link = '$oldLink') AND
4456
                        image = 'scormbuilder.gif' AND
4457
                        (
4458
                            link LIKE '$link%' OR
4459
                            link LIKE '$oldLink%'
4460
                        )
4461
                        $session_condition
4462
                    ";
4463
4464
            $result = Database::query($sql);
4465
            $num = Database::num_rows($result);
4466
            if ($set_visibility == 'i' && $num > 0) {
4467
                $sql = "DELETE FROM $tbl_tool
4468
                        WHERE
4469
                            c_id = $course_id AND
4470
                            (link = '$link' OR link = '$oldLink') AND
4471
                            image='scormbuilder.gif'
4472
                            $session_condition";
4473
                Database::query($sql);
4474
            } elseif ($set_visibility == 'v' && $num == 0) {
4475
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4476
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4477
                Database::query($sql);
4478
                $insertId = Database::insert_id();
4479
                if ($insertId) {
4480
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4481
                    Database::query($sql);
4482
                }
4483
            } elseif ($set_visibility == 'v' && $num > 0) {
4484
                $sql = "UPDATE $tbl_tool SET
4485
                            c_id = $course_id,
4486
                            name = '$name',
4487
                            link = '$link',
4488
                            image = 'scormbuilder.gif',
4489
                            visibility = '$v',
4490
                            admin = '0',
4491
                            address = 'pastillegris.gif',
4492
                            added_tool = 0,
4493
                            session_id = $session_id
4494
                        WHERE
4495
                            c_id = ".$course_id." AND
4496
                            (link = '$link' OR link = '$oldLink') AND
4497
                            image='scormbuilder.gif'
4498
                            $session_condition
4499
                        ";
4500
                Database::query($sql);
4501
            } else {
4502
                // Parameter and database incompatible, do nothing, exit.
4503
                return false;
4504
            }
4505
        } else {
4506
            return false;
4507
        }
4508
    }
4509
4510
    /**
4511
     * Publishes a learnpath.
4512
     * Show or hide the learnpath category on the course homepage.
4513
     *
4514
     * @param int $id
4515
     * @param int $setVisibility
4516
     *
4517
     * @throws \Doctrine\ORM\NonUniqueResultException
4518
     * @throws \Doctrine\ORM\ORMException
4519
     * @throws \Doctrine\ORM\OptimisticLockException
4520
     * @throws \Doctrine\ORM\TransactionRequiredException
4521
     *
4522
     * @return bool
4523
     */
4524
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4525
    {
4526
        $courseId = api_get_course_int_id();
4527
        $sessionId = api_get_session_id();
4528
        $sessionCondition = api_get_session_condition(
4529
            $sessionId,
4530
            true,
4531
            false,
4532
            't.sessionId'
4533
        );
4534
4535
        $em = Database::getManager();
4536
4537
        /** @var CLpCategory $category */
4538
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4539
4540
        if (!$category) {
4541
            return false;
4542
        }
4543
4544
        if (empty($courseId)) {
4545
            return false;
4546
        }
4547
4548
        $link = self::getCategoryLinkForTool($id);
4549
4550
        /** @var CTool $tool */
4551
        $tool = $em->createQuery("
4552
                SELECT t FROM ChamiloCourseBundle:CTool t
4553
                WHERE
4554
                    t.cId = :course AND
4555
                    t.link = :link1 AND
4556
                    t.image = 'lp_category.gif' AND
4557
                    t.link LIKE :link2
4558
                    $sessionCondition
4559
            ")
4560
            ->setParameters([
4561
                'course' => $courseId,
4562
                'link1' => $link,
4563
                'link2' => "$link%",
4564
            ])
4565
            ->getOneOrNullResult();
4566
4567
        if ($setVisibility == 0 && $tool) {
4568
            $em->remove($tool);
4569
            $em->flush();
4570
4571
            return true;
4572
        }
4573
4574
        if ($setVisibility == 1 && !$tool) {
4575
            $tool = new CTool();
4576
            $tool
4577
                ->setCategory('authoring')
4578
                ->setCId($courseId)
4579
                ->setName(strip_tags($category->getName()))
4580
                ->setLink($link)
4581
                ->setImage('lp_category.gif')
4582
                ->setVisibility(1)
4583
                ->setAdmin(0)
4584
                ->setAddress('pastillegris.gif')
4585
                ->setAddedTool(0)
4586
                ->setSessionId($sessionId)
4587
                ->setTarget('_self');
4588
4589
            $em->persist($tool);
4590
            $em->flush();
4591
4592
            $tool->setId($tool->getIid());
4593
4594
            $em->persist($tool);
4595
            $em->flush();
4596
4597
            return true;
4598
        }
4599
4600
        if ($setVisibility == 1 && $tool) {
4601
            $tool
4602
                ->setName(strip_tags($category->getName()))
4603
                ->setVisibility(1);
4604
4605
            $em->persist($tool);
4606
            $em->flush();
4607
4608
            return true;
4609
        }
4610
4611
        return false;
4612
    }
4613
4614
    /**
4615
     * Check if the learnpath category is visible for a user.
4616
     *
4617
     * @param int
4618
     * @param int
4619
     *
4620
     * @return bool
4621
     */
4622
    public static function categoryIsVisibleForStudent(
4623
        CLpCategory $category,
4624
        User $user,
4625
        $courseId = 0,
4626
        $sessionId = 0
4627
    ) {
4628
        if (empty($category)) {
4629
            return false;
4630
        }
4631
4632
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4633
4634
        if ($isAllowedToEdit) {
4635
            return true;
4636
        }
4637
4638
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4639
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4640
4641
        $courseInfo = api_get_course_info_by_id($courseId);
4642
4643
        $categoryVisibility = api_get_item_visibility(
4644
            $courseInfo,
4645
            TOOL_LEARNPATH_CATEGORY,
4646
            $category->getId(),
4647
            $sessionId
4648
        );
4649
4650
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4651
            return false;
4652
        }
4653
4654
        $subscriptionSettings = self::getSubscriptionSettings();
4655
4656
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4657
            return true;
4658
        }
4659
4660
        $noUserSubscribed = false;
4661
        $noGroupSubscribed = true;
4662
        $users = $category->getUsers();
4663
        if (empty($users) || !$users->count()) {
4664
            $noUserSubscribed = true;
4665
        } elseif ($category->hasUserAdded($user)) {
4666
            return true;
4667
        }
4668
4669
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4670
        $em = Database::getManager();
4671
4672
        /** @var ItemPropertyRepository $itemRepo */
4673
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4674
4675
        /** @var CourseRepository $courseRepo */
4676
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4677
        $session = null;
4678
        if (!empty($sessionId)) {
4679
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4680
        }
4681
4682
        $course = $courseRepo->find($courseId);
4683
4684
        if ($courseId != 0) {
4685
            // Subscribed groups to a LP
4686
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4687
                TOOL_LEARNPATH_CATEGORY,
4688
                $category->getId(),
4689
                $course,
4690
                $session
4691
            );
4692
        }
4693
4694
        if (!empty($subscribedGroupsInLp)) {
4695
            $noGroupSubscribed = false;
4696
            if (!empty($groups)) {
4697
                $groups = array_column($groups, 'iid');
4698
                /** @var CItemProperty $item */
4699
                foreach ($subscribedGroupsInLp as $item) {
4700
                    if ($item->getGroup() &&
4701
                        in_array($item->getGroup()->getId(), $groups)
4702
                    ) {
4703
                        return true;
4704
                    }
4705
                }
4706
            }
4707
        }
4708
        $response = $noGroupSubscribed && $noUserSubscribed;
4709
4710
        return $response;
4711
    }
4712
4713
    /**
4714
     * Check if a learnpath category is published as course tool.
4715
     *
4716
     * @param int $courseId
4717
     *
4718
     * @return bool
4719
     */
4720
    public static function categoryIsPublished(
4721
        CLpCategory $category,
4722
        $courseId
4723
    ) {
4724
        $link = self::getCategoryLinkForTool($category->getId());
4725
        $em = Database::getManager();
4726
4727
        $tools = $em
4728
            ->createQuery("
4729
                SELECT t FROM ChamiloCourseBundle:CTool t
4730
                WHERE t.cId = :course AND
4731
                    t.name = :name AND
4732
                    t.image = 'lp_category.gif' AND
4733
                    t.link LIKE :link
4734
            ")
4735
            ->setParameters([
4736
                'course' => $courseId,
4737
                'name' => strip_tags($category->getName()),
4738
                'link' => "$link%",
4739
            ])
4740
            ->getResult();
4741
4742
        /** @var CTool $tool */
4743
        $tool = current($tools);
4744
4745
        return $tool ? $tool->getVisibility() : false;
4746
    }
4747
4748
    /**
4749
     * Restart the whole learnpath. Return the URL of the first element.
4750
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4751
     * To use a similar method  statically, use the create_new_attempt() method.
4752
     *
4753
     * @return bool
4754
     */
4755
    public function restart()
4756
    {
4757
        if ($this->debug > 0) {
4758
            error_log('In learnpath::restart()', 0);
4759
        }
4760
        // TODO
4761
        // Call autosave method to save the current progress.
4762
        //$this->index = 0;
4763
        if (api_is_invitee()) {
4764
            return false;
4765
        }
4766
        $session_id = api_get_session_id();
4767
        $course_id = api_get_course_int_id();
4768
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4769
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4770
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4771
        if ($this->debug > 2) {
4772
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4773
        }
4774
        Database::query($sql);
4775
        $view_id = Database::insert_id();
4776
4777
        if ($view_id) {
4778
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4779
            Database::query($sql);
4780
            $this->lp_view_id = $view_id;
4781
            $this->attempt = $this->attempt + 1;
4782
        } else {
4783
            $this->error = 'Could not insert into item_view table...';
4784
4785
            return false;
4786
        }
4787
        $this->autocomplete_parents($this->current);
4788
        foreach ($this->items as $index => $dummy) {
4789
            $this->items[$index]->restart();
4790
            $this->items[$index]->set_lp_view($this->lp_view_id);
4791
        }
4792
        $this->first();
4793
4794
        return true;
4795
    }
4796
4797
    /**
4798
     * Saves the current item.
4799
     *
4800
     * @return bool
4801
     */
4802
    public function save_current()
4803
    {
4804
        $debug = $this->debug;
4805
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4806
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4807
        if ($debug) {
4808
            error_log('save_current() saving item '.$this->current, 0);
4809
            error_log(''.print_r($this->items, true), 0);
4810
        }
4811
        if (isset($this->items[$this->current]) &&
4812
            is_object($this->items[$this->current])
4813
        ) {
4814
            if ($debug) {
4815
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4816
            }
4817
4818
            $res = $this->items[$this->current]->save(
4819
                false,
4820
                $this->prerequisites_match($this->current)
4821
            );
4822
            $this->autocomplete_parents($this->current);
4823
            $status = $this->items[$this->current]->get_status();
4824
            $this->update_queue[$this->current] = $status;
4825
4826
            if ($debug) {
4827
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4828
            }
4829
4830
            return $res;
4831
        }
4832
4833
        return false;
4834
    }
4835
4836
    /**
4837
     * Saves the given item.
4838
     *
4839
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4840
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4841
     *
4842
     * @return bool
4843
     */
4844
    public function save_item($item_id = null, $from_outside = true)
4845
    {
4846
        $debug = $this->debug;
4847
        if ($debug) {
4848
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4849
        }
4850
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4851
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4852
        if (empty($item_id)) {
4853
            $item_id = (int) $_REQUEST['id'];
4854
        }
4855
4856
        if (empty($item_id)) {
4857
            $item_id = $this->get_current_item_id();
4858
        }
4859
        if (isset($this->items[$item_id]) &&
4860
            is_object($this->items[$item_id])
4861
        ) {
4862
            if ($debug) {
4863
                error_log('Object exists');
4864
            }
4865
4866
            // Saving the item.
4867
            $res = $this->items[$item_id]->save(
4868
                $from_outside,
4869
                $this->prerequisites_match($item_id)
4870
            );
4871
4872
            if ($debug) {
4873
                error_log('update_queue before:');
4874
                error_log(print_r($this->update_queue, 1));
4875
            }
4876
            $this->autocomplete_parents($item_id);
4877
4878
            $status = $this->items[$item_id]->get_status();
4879
            $this->update_queue[$item_id] = $status;
4880
4881
            if ($debug) {
4882
                error_log('get_status(): '.$status);
4883
                error_log('update_queue after:');
4884
                error_log(print_r($this->update_queue, 1));
4885
            }
4886
4887
            return $res;
4888
        }
4889
4890
        return false;
4891
    }
4892
4893
    /**
4894
     * Saves the last item seen's ID only in case.
4895
     */
4896
    public function save_last()
4897
    {
4898
        $course_id = api_get_course_int_id();
4899
        $debug = $this->debug;
4900
        if ($debug) {
4901
            error_log('In learnpath::save_last()', 0);
4902
        }
4903
        $session_condition = api_get_session_condition(
4904
            api_get_session_id(),
4905
            true,
4906
            false
4907
        );
4908
        $table = Database::get_course_table(TABLE_LP_VIEW);
4909
4910
        $userId = $this->get_user_id();
4911
        if (empty($userId)) {
4912
            $userId = api_get_user_id();
4913
            if ($debug) {
4914
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4915
            }
4916
        }
4917
        if (isset($this->current) && !api_is_invitee()) {
4918
            if ($debug) {
4919
                error_log('Saving current item ('.$this->current.') for later review', 0);
4920
            }
4921
            $sql = "UPDATE $table SET
4922
                        last_item = ".$this->get_current_item_id()."
4923
                    WHERE
4924
                        c_id = $course_id AND
4925
                        lp_id = ".$this->get_id()." AND
4926
                        user_id = ".$userId." ".$session_condition;
4927
4928
            if ($debug) {
4929
                error_log('Saving last item seen : '.$sql, 0);
4930
            }
4931
            Database::query($sql);
4932
        }
4933
4934
        if (!api_is_invitee()) {
4935
            // Save progress.
4936
            list($progress) = $this->get_progress_bar_text('%');
4937
            if ($progress >= 0 && $progress <= 100) {
4938
                $progress = (int) $progress;
4939
                $sql = "UPDATE $table SET
4940
                            progress = $progress
4941
                        WHERE
4942
                            c_id = $course_id AND
4943
                            lp_id = ".$this->get_id()." AND
4944
                            user_id = ".$userId." ".$session_condition;
4945
                // Ignore errors as some tables might not have the progress field just yet.
4946
                Database::query($sql);
4947
                $this->progress_db = $progress;
4948
            }
4949
        }
4950
    }
4951
4952
    /**
4953
     * Sets the current item ID (checks if valid and authorized first).
4954
     *
4955
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4956
     */
4957
    public function set_current_item($item_id = null)
4958
    {
4959
        $debug = $this->debug;
4960
        if ($debug) {
4961
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4962
        }
4963
        if (empty($item_id)) {
4964
            if ($debug) {
4965
                error_log('No new current item given, ignore...', 0);
4966
            }
4967
            // Do nothing.
4968
        } else {
4969
            if ($debug) {
4970
                error_log('New current item given is '.$item_id.'...', 0);
4971
            }
4972
            if (is_numeric($item_id)) {
4973
                $item_id = (int) $item_id;
4974
                // TODO: Check in database here.
4975
                $this->last = $this->current;
4976
                $this->current = $item_id;
4977
                // TODO: Update $this->index as well.
4978
                foreach ($this->ordered_items as $index => $item) {
4979
                    if ($item == $this->current) {
4980
                        $this->index = $index;
4981
                        break;
4982
                    }
4983
                }
4984
                if ($debug) {
4985
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4986
                }
4987
            } else {
4988
                if ($debug) {
4989
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4990
                }
4991
            }
4992
        }
4993
    }
4994
4995
    /**
4996
     * Sets the encoding.
4997
     *
4998
     * @param string $enc New encoding
4999
     *
5000
     * @return bool
5001
     *
5002
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5003
     */
5004
    public function set_encoding($enc = 'UTF-8')
5005
    {
5006
        $enc = api_refine_encoding_id($enc);
5007
        if (empty($enc)) {
5008
            $enc = api_get_system_encoding();
5009
        }
5010
        if (api_is_encoding_supported($enc)) {
5011
            $lp = $this->get_id();
5012
            if ($lp != 0) {
5013
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5014
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5015
                        WHERE iid = ".$lp;
5016
                $res = Database::query($sql);
5017
5018
                return $res;
5019
            }
5020
        }
5021
5022
        return false;
5023
    }
5024
5025
    /**
5026
     * Sets the JS lib setting in the database directly.
5027
     * This is the JavaScript library file this lp needs to load on startup.
5028
     *
5029
     * @param string $lib Proximity setting
5030
     *
5031
     * @return bool True on update success. False otherwise.
5032
     */
5033
    public function set_jslib($lib = '')
5034
    {
5035
        $lp = $this->get_id();
5036
5037
        if ($lp != 0) {
5038
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5039
            $lib = Database::escape_string($lib);
5040
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5041
                    WHERE iid = $lp";
5042
            $res = Database::query($sql);
5043
5044
            return $res;
5045
        }
5046
5047
        return false;
5048
    }
5049
5050
    /**
5051
     * Sets the name of the LP maker (publisher) (and save).
5052
     *
5053
     * @param string $name Optional string giving the new content_maker of this learnpath
5054
     *
5055
     * @return bool True
5056
     */
5057
    public function set_maker($name = '')
5058
    {
5059
        if (empty($name)) {
5060
            return false;
5061
        }
5062
        $this->maker = $name;
5063
        $table = Database::get_course_table(TABLE_LP_MAIN);
5064
        $lp_id = $this->get_id();
5065
        $sql = "UPDATE $table SET
5066
                content_maker = '".Database::escape_string($this->maker)."'
5067
                WHERE iid = $lp_id";
5068
        Database::query($sql);
5069
5070
        return true;
5071
    }
5072
5073
    /**
5074
     * Sets the name of the current learnpath (and save).
5075
     *
5076
     * @param string $name Optional string giving the new name of this learnpath
5077
     *
5078
     * @return bool True/False
5079
     */
5080
    public function set_name($name = null)
5081
    {
5082
        if (empty($name)) {
5083
            return false;
5084
        }
5085
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5086
        $name = Database::escape_string($name);
5087
5088
        $this->name = $name;
5089
5090
        $lp_id = $this->get_id();
5091
        $course_id = $this->course_info['real_id'];
5092
        $sql = "UPDATE $lp_table SET
5093
                name = '$name'
5094
                WHERE iid = $lp_id";
5095
        $result = Database::query($sql);
5096
        // If the lp is visible on the homepage, change his name there.
5097
        if (Database::affected_rows($result)) {
5098
            $session_id = api_get_session_id();
5099
            $session_condition = api_get_session_condition($session_id);
5100
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5101
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5102
            $sql = "UPDATE $tbl_tool SET name = '$name'
5103
            	    WHERE
5104
            	        c_id = $course_id AND
5105
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5106
            Database::query($sql);
5107
5108
            return true;
5109
        }
5110
5111
        return false;
5112
    }
5113
5114
    /**
5115
     * Set index specified prefix terms for all items in this path.
5116
     *
5117
     * @param string $terms_string Comma-separated list of terms
5118
     * @param string $prefix       Xapian term prefix
5119
     *
5120
     * @return bool False on error, true otherwise
5121
     */
5122
    public function set_terms_by_prefix($terms_string, $prefix)
5123
    {
5124
        $course_id = api_get_course_int_id();
5125
        if (api_get_setting('search_enabled') !== 'true') {
5126
            return false;
5127
        }
5128
5129
        if (!extension_loaded('xapian')) {
5130
            return false;
5131
        }
5132
5133
        $terms_string = trim($terms_string);
5134
        $terms = explode(',', $terms_string);
5135
        array_walk($terms, 'trim_value');
5136
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5137
5138
        // Don't do anything if no change, verify only at DB, not the search engine.
5139
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5140
            return false;
5141
        }
5142
5143
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5144
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5145
5146
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5147
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5148
        $lp_id = (int) $_POST['lp_id'];
5149
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5150
        $result = Database::query($sql);
5151
        $di = new ChamiloIndexer();
5152
5153
        while ($lp_item = Database::fetch_array($result)) {
5154
            // Get search_did.
5155
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5156
            $sql = 'SELECT * FROM %s
5157
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5158
                    LIMIT 1';
5159
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5160
5161
            //echo $sql; echo '<br>';
5162
            $res = Database::query($sql);
5163
            if (Database::num_rows($res) > 0) {
5164
                $se_ref = Database::fetch_array($res);
5165
                // Compare terms.
5166
                $doc = $di->get_document($se_ref['search_did']);
5167
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5168
                $xterms = [];
5169
                foreach ($xapian_terms as $xapian_term) {
5170
                    $xterms[] = substr($xapian_term['name'], 1);
5171
                }
5172
5173
                $dterms = $terms;
5174
                $missing_terms = array_diff($dterms, $xterms);
5175
                $deprecated_terms = array_diff($xterms, $dterms);
5176
5177
                // Save it to search engine.
5178
                foreach ($missing_terms as $term) {
5179
                    $doc->add_term($prefix.$term, 1);
5180
                }
5181
                foreach ($deprecated_terms as $term) {
5182
                    $doc->remove_term($prefix.$term);
5183
                }
5184
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5185
                $di->getDb()->flush();
5186
            }
5187
        }
5188
5189
        return true;
5190
    }
5191
5192
    /**
5193
     * Sets the theme of the LP (local/remote) (and save).
5194
     *
5195
     * @param string $name Optional string giving the new theme of this learnpath
5196
     *
5197
     * @return bool Returns true if theme name is not empty
5198
     */
5199
    public function set_theme($name = '')
5200
    {
5201
        $this->theme = $name;
5202
        $table = Database::get_course_table(TABLE_LP_MAIN);
5203
        $lp_id = $this->get_id();
5204
        $sql = "UPDATE $table
5205
                SET theme = '".Database::escape_string($this->theme)."'
5206
                WHERE iid = $lp_id";
5207
        Database::query($sql);
5208
5209
        return true;
5210
    }
5211
5212
    /**
5213
     * Sets the image of an LP (and save).
5214
     *
5215
     * @param string $name Optional string giving the new image of this learnpath
5216
     *
5217
     * @return bool Returns true if theme name is not empty
5218
     */
5219
    public function set_preview_image($name = '')
5220
    {
5221
        $this->preview_image = $name;
5222
        $table = Database::get_course_table(TABLE_LP_MAIN);
5223
        $lp_id = $this->get_id();
5224
        $sql = "UPDATE $table SET
5225
                preview_image = '".Database::escape_string($this->preview_image)."'
5226
                WHERE iid = $lp_id";
5227
        Database::query($sql);
5228
5229
        return true;
5230
    }
5231
5232
    /**
5233
     * Sets the author of a LP (and save).
5234
     *
5235
     * @param string $name Optional string giving the new author of this learnpath
5236
     *
5237
     * @return bool Returns true if author's name is not empty
5238
     */
5239
    public function set_author($name = '')
5240
    {
5241
        $this->author = $name;
5242
        $table = Database::get_course_table(TABLE_LP_MAIN);
5243
        $lp_id = $this->get_id();
5244
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5245
                WHERE iid = $lp_id";
5246
        Database::query($sql);
5247
5248
        return true;
5249
    }
5250
5251
    /**
5252
     * Sets the hide_toc_frame parameter of a LP (and save).
5253
     *
5254
     * @param int $hide 1 if frame is hidden 0 then else
5255
     *
5256
     * @return bool Returns true if author's name is not empty
5257
     */
5258
    public function set_hide_toc_frame($hide)
5259
    {
5260
        if (intval($hide) == $hide) {
5261
            $this->hide_toc_frame = $hide;
5262
            $table = Database::get_course_table(TABLE_LP_MAIN);
5263
            $lp_id = $this->get_id();
5264
            $sql = "UPDATE $table SET
5265
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5266
                    WHERE iid = $lp_id";
5267
            Database::query($sql);
5268
5269
            return true;
5270
        }
5271
5272
        return false;
5273
    }
5274
5275
    /**
5276
     * Sets the prerequisite of a LP (and save).
5277
     *
5278
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5279
     *
5280
     * @return bool returns true if prerequisite is not empty
5281
     */
5282
    public function set_prerequisite($prerequisite)
5283
    {
5284
        $this->prerequisite = (int) $prerequisite;
5285
        $table = Database::get_course_table(TABLE_LP_MAIN);
5286
        $lp_id = $this->get_id();
5287
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5288
                WHERE iid = $lp_id";
5289
        Database::query($sql);
5290
5291
        return true;
5292
    }
5293
5294
    /**
5295
     * Sets the location/proximity of the LP (local/remote) (and save).
5296
     *
5297
     * @param string $name Optional string giving the new location of this learnpath
5298
     *
5299
     * @return bool True on success / False on error
5300
     */
5301
    public function set_proximity($name = '')
5302
    {
5303
        if (empty($name)) {
5304
            return false;
5305
        }
5306
5307
        $this->proximity = $name;
5308
        $table = Database::get_course_table(TABLE_LP_MAIN);
5309
        $lp_id = $this->get_id();
5310
        $sql = "UPDATE $table SET
5311
                    content_local = '".Database::escape_string($name)."'
5312
                WHERE iid = $lp_id";
5313
        Database::query($sql);
5314
5315
        return true;
5316
    }
5317
5318
    /**
5319
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5320
     *
5321
     * @param int $id DB ID of the item
5322
     */
5323
    public function set_previous_item($id)
5324
    {
5325
        if ($this->debug > 0) {
5326
            error_log('In learnpath::set_previous_item()', 0);
5327
        }
5328
        $this->last = $id;
5329
    }
5330
5331
    /**
5332
     * Sets use_max_score.
5333
     *
5334
     * @param int $use_max_score Optional string giving the new location of this learnpath
5335
     *
5336
     * @return bool True on success / False on error
5337
     */
5338
    public function set_use_max_score($use_max_score = 1)
5339
    {
5340
        $use_max_score = (int) $use_max_score;
5341
        $this->use_max_score = $use_max_score;
5342
        $table = Database::get_course_table(TABLE_LP_MAIN);
5343
        $lp_id = $this->get_id();
5344
        $sql = "UPDATE $table SET
5345
                    use_max_score = '".$this->use_max_score."'
5346
                WHERE iid = $lp_id";
5347
        Database::query($sql);
5348
5349
        return true;
5350
    }
5351
5352
    /**
5353
     * Sets and saves the expired_on date.
5354
     *
5355
     * @param string $expired_on Optional string giving the new author of this learnpath
5356
     *
5357
     * @throws \Doctrine\ORM\OptimisticLockException
5358
     *
5359
     * @return bool Returns true if author's name is not empty
5360
     */
5361
    public function set_expired_on($expired_on)
5362
    {
5363
        $em = Database::getManager();
5364
        /** @var CLp $lp */
5365
        $lp = $em
5366
            ->getRepository('ChamiloCourseBundle:CLp')
5367
            ->findOneBy(
5368
                [
5369
                    'iid' => $this->get_id(),
5370
                ]
5371
            );
5372
5373
        if (!$lp) {
5374
            return false;
5375
        }
5376
5377
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5378
5379
        $lp->setExpiredOn($this->expired_on);
5380
        $em->persist($lp);
5381
        $em->flush();
5382
5383
        return true;
5384
    }
5385
5386
    /**
5387
     * Sets and saves the publicated_on date.
5388
     *
5389
     * @param string $publicated_on Optional string giving the new author of this learnpath
5390
     *
5391
     * @throws \Doctrine\ORM\OptimisticLockException
5392
     *
5393
     * @return bool Returns true if author's name is not empty
5394
     */
5395
    public function set_publicated_on($publicated_on)
5396
    {
5397
        $em = Database::getManager();
5398
        /** @var CLp $lp */
5399
        $lp = $em
5400
            ->getRepository('ChamiloCourseBundle:CLp')
5401
            ->findOneBy(
5402
                [
5403
                    'iid' => $this->get_id(),
5404
                ]
5405
            );
5406
5407
        if (!$lp) {
5408
            return false;
5409
        }
5410
5411
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5412
        $lp->setPublicatedOn($this->publicated_on);
5413
        $em->persist($lp);
5414
        $em->flush();
5415
5416
        return true;
5417
    }
5418
5419
    /**
5420
     * Sets and saves the expired_on date.
5421
     *
5422
     * @return bool Returns true if author's name is not empty
5423
     */
5424
    public function set_modified_on()
5425
    {
5426
        $this->modified_on = api_get_utc_datetime();
5427
        $table = Database::get_course_table(TABLE_LP_MAIN);
5428
        $lp_id = $this->get_id();
5429
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5430
                WHERE iid = $lp_id";
5431
        Database::query($sql);
5432
5433
        return true;
5434
    }
5435
5436
    /**
5437
     * Sets the object's error message.
5438
     *
5439
     * @param string $error Error message. If empty, reinits the error string
5440
     */
5441
    public function set_error_msg($error = '')
5442
    {
5443
        if ($this->debug > 0) {
5444
            error_log('In learnpath::set_error_msg()', 0);
5445
        }
5446
        if (empty($error)) {
5447
            $this->error = '';
5448
        } else {
5449
            $this->error .= $error;
5450
        }
5451
    }
5452
5453
    /**
5454
     * Launches the current item if not 'sco'
5455
     * (starts timer and make sure there is a record ready in the DB).
5456
     *
5457
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5458
     *
5459
     * @return bool
5460
     */
5461
    public function start_current_item($allow_new_attempt = false)
5462
    {
5463
        $debug = $this->debug;
5464
        if ($debug) {
5465
            error_log('In learnpath::start_current_item()');
5466
            error_log('current: '.$this->current);
5467
        }
5468
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5469
            $type = $this->get_type();
5470
            $item_type = $this->items[$this->current]->get_type();
5471
            if (($type == 2 && $item_type != 'sco') ||
5472
                ($type == 3 && $item_type != 'au') ||
5473
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5474
            ) {
5475
                if ($debug) {
5476
                    error_log('item type: '.$item_type);
5477
                    error_log('lp type: '.$type);
5478
                }
5479
                $this->items[$this->current]->open($allow_new_attempt);
5480
                $this->autocomplete_parents($this->current);
5481
                $prereq_check = $this->prerequisites_match($this->current);
5482
                if ($debug) {
5483
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5484
                }
5485
                $this->items[$this->current]->save(false, $prereq_check);
5486
            }
5487
            // If sco, then it is supposed to have been updated by some other call.
5488
            if ($item_type == 'sco') {
5489
                $this->items[$this->current]->restart();
5490
            }
5491
        }
5492
        if ($debug) {
5493
            error_log('lp_view_session_id');
5494
            error_log($this->lp_view_session_id);
5495
            error_log('api session id');
5496
            error_log(api_get_session_id());
5497
            error_log('End of learnpath::start_current_item()');
5498
        }
5499
5500
        return true;
5501
    }
5502
5503
    /**
5504
     * Stops the processing and counters for the old item (as held in $this->last).
5505
     *
5506
     * @return bool True/False
5507
     */
5508
    public function stop_previous_item()
5509
    {
5510
        $debug = $this->debug;
5511
        if ($debug) {
5512
            error_log('In learnpath::stop_previous_item()', 0);
5513
        }
5514
5515
        if ($this->last != 0 && $this->last != $this->current &&
5516
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5517
        ) {
5518
            if ($debug) {
5519
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5520
            }
5521
            switch ($this->get_type()) {
5522
                case '3':
5523
                    if ($this->items[$this->last]->get_type() != 'au') {
5524
                        if ($debug) {
5525
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5526
                        }
5527
                        $this->items[$this->last]->close();
5528
                    } else {
5529
                        if ($debug) {
5530
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5531
                        }
5532
                    }
5533
                    break;
5534
                case '2':
5535
                    if ($this->items[$this->last]->get_type() != 'sco') {
5536
                        if ($debug) {
5537
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5538
                        }
5539
                        $this->items[$this->last]->close();
5540
                    } else {
5541
                        if ($debug) {
5542
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5543
                        }
5544
                    }
5545
                    break;
5546
                case '1':
5547
                default:
5548
                    if ($debug) {
5549
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5550
                    }
5551
                    $this->items[$this->last]->close();
5552
                    break;
5553
            }
5554
        } else {
5555
            if ($debug) {
5556
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5557
            }
5558
5559
            return false;
5560
        }
5561
5562
        return true;
5563
    }
5564
5565
    /**
5566
     * Updates the default view mode from fullscreen to embedded and inversely.
5567
     *
5568
     * @return string The current default view mode ('fullscreen' or 'embedded')
5569
     */
5570
    public function update_default_view_mode()
5571
    {
5572
        $table = Database::get_course_table(TABLE_LP_MAIN);
5573
        $sql = "SELECT * FROM $table
5574
                WHERE iid = ".$this->get_id();
5575
        $res = Database::query($sql);
5576
        if (Database::num_rows($res) > 0) {
5577
            $row = Database::fetch_array($res);
5578
            $default_view_mode = $row['default_view_mod'];
5579
            $view_mode = $default_view_mode;
5580
            switch ($default_view_mode) {
5581
                case 'fullscreen': // default with popup
5582
                    $view_mode = 'embedded';
5583
                    break;
5584
                case 'embedded': // default view with left menu
5585
                    $view_mode = 'embedframe';
5586
                    break;
5587
                case 'embedframe': //folded menu
5588
                    $view_mode = 'impress';
5589
                    break;
5590
                case 'impress':
5591
                    $view_mode = 'fullscreen';
5592
                    break;
5593
            }
5594
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5595
                    WHERE iid = ".$this->get_id();
5596
            Database::query($sql);
5597
            $this->mode = $view_mode;
5598
5599
            return $view_mode;
5600
        }
5601
5602
        return -1;
5603
    }
5604
5605
    /**
5606
     * Updates the default behaviour about auto-commiting SCORM updates.
5607
     *
5608
     * @return bool True if auto-commit has been set to 'on', false otherwise
5609
     */
5610
    public function update_default_scorm_commit()
5611
    {
5612
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5613
        $sql = "SELECT * FROM $lp_table
5614
                WHERE iid = ".$this->get_id();
5615
        $res = Database::query($sql);
5616
        if (Database::num_rows($res) > 0) {
5617
            $row = Database::fetch_array($res);
5618
            $force = $row['force_commit'];
5619
            if ($force == 1) {
5620
                $force = 0;
5621
                $force_return = false;
5622
            } elseif ($force == 0) {
5623
                $force = 1;
5624
                $force_return = true;
5625
            }
5626
            $sql = "UPDATE $lp_table SET force_commit = $force
5627
                    WHERE iid = ".$this->get_id();
5628
            Database::query($sql);
5629
            $this->force_commit = $force_return;
5630
5631
            return $force_return;
5632
        }
5633
5634
        return -1;
5635
    }
5636
5637
    /**
5638
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5639
     *
5640
     * @return bool True on success, false on failure
5641
     */
5642
    public function update_display_order()
5643
    {
5644
        $course_id = api_get_course_int_id();
5645
        $table = Database::get_course_table(TABLE_LP_MAIN);
5646
        $sql = "SELECT * FROM $table
5647
                WHERE c_id = $course_id
5648
                ORDER BY display_order";
5649
        $res = Database::query($sql);
5650
        if ($res === false) {
5651
            return false;
5652
        }
5653
5654
        $num = Database::num_rows($res);
5655
        // First check the order is correct, globally (might be wrong because
5656
        // of versions < 1.8.4).
5657
        if ($num > 0) {
5658
            $i = 1;
5659
            while ($row = Database::fetch_array($res)) {
5660
                if ($row['display_order'] != $i) {
5661
                    // If we find a gap in the order, we need to fix it.
5662
                    $sql = "UPDATE $table SET display_order = $i
5663
                            WHERE iid = ".$row['iid'];
5664
                    Database::query($sql);
5665
                }
5666
                $i++;
5667
            }
5668
        }
5669
5670
        return true;
5671
    }
5672
5673
    /**
5674
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5675
     *
5676
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5677
     */
5678
    public function update_reinit()
5679
    {
5680
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5681
        $sql = "SELECT * FROM $lp_table
5682
                WHERE iid = ".$this->get_id();
5683
        $res = Database::query($sql);
5684
        if (Database::num_rows($res) > 0) {
5685
            $row = Database::fetch_array($res);
5686
            $force = $row['prevent_reinit'];
5687
            if ($force == 1) {
5688
                $force = 0;
5689
            } elseif ($force == 0) {
5690
                $force = 1;
5691
            }
5692
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5693
                    WHERE iid = ".$this->get_id();
5694
            Database::query($sql);
5695
            $this->prevent_reinit = $force;
5696
5697
            return $force;
5698
        }
5699
5700
        return -1;
5701
    }
5702
5703
    /**
5704
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5705
     *
5706
     * @return string 'single', 'multi' or 'seriousgame'
5707
     *
5708
     * @author ndiechburg <[email protected]>
5709
     */
5710
    public function get_attempt_mode()
5711
    {
5712
        //Set default value for seriousgame_mode
5713
        if (!isset($this->seriousgame_mode)) {
5714
            $this->seriousgame_mode = 0;
5715
        }
5716
        // Set default value for prevent_reinit
5717
        if (!isset($this->prevent_reinit)) {
5718
            $this->prevent_reinit = 1;
5719
        }
5720
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5721
            return 'seriousgame';
5722
        }
5723
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5724
            return 'single';
5725
        }
5726
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5727
            return 'multiple';
5728
        }
5729
5730
        return 'single';
5731
    }
5732
5733
    /**
5734
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5735
     *
5736
     * @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...
5737
     *
5738
     * @return bool
5739
     *
5740
     * @author ndiechburg <[email protected]>
5741
     */
5742
    public function set_attempt_mode($mode)
5743
    {
5744
        switch ($mode) {
5745
            case 'seriousgame':
5746
                $sg_mode = 1;
5747
                $prevent_reinit = 1;
5748
                break;
5749
            case 'single':
5750
                $sg_mode = 0;
5751
                $prevent_reinit = 1;
5752
                break;
5753
            case 'multiple':
5754
                $sg_mode = 0;
5755
                $prevent_reinit = 0;
5756
                break;
5757
            default:
5758
                $sg_mode = 0;
5759
                $prevent_reinit = 0;
5760
                break;
5761
        }
5762
        $this->prevent_reinit = $prevent_reinit;
5763
        $this->seriousgame_mode = $sg_mode;
5764
        $table = Database::get_course_table(TABLE_LP_MAIN);
5765
        $sql = "UPDATE $table SET
5766
                prevent_reinit = $prevent_reinit ,
5767
                seriousgame_mode = $sg_mode
5768
                WHERE iid = ".$this->get_id();
5769
        $res = Database::query($sql);
5770
        if ($res) {
5771
            return true;
5772
        } else {
5773
            return false;
5774
        }
5775
    }
5776
5777
    /**
5778
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5779
     *
5780
     * @author ndiechburg <[email protected]>
5781
     */
5782
    public function switch_attempt_mode()
5783
    {
5784
        $mode = $this->get_attempt_mode();
5785
        switch ($mode) {
5786
            case 'single':
5787
                $next_mode = 'multiple';
5788
                break;
5789
            case 'multiple':
5790
                $next_mode = 'seriousgame';
5791
                break;
5792
            case 'seriousgame':
5793
            default:
5794
                $next_mode = 'single';
5795
                break;
5796
        }
5797
        $this->set_attempt_mode($next_mode);
5798
    }
5799
5800
    /**
5801
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5802
     * but possibility to do again a completed item.
5803
     *
5804
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5805
     *
5806
     * @author ndiechburg <[email protected]>
5807
     */
5808
    public function set_seriousgame_mode()
5809
    {
5810
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5811
        $sql = "SELECT * FROM $lp_table
5812
                WHERE iid = ".$this->get_id();
5813
        $res = Database::query($sql);
5814
        if (Database::num_rows($res) > 0) {
5815
            $row = Database::fetch_array($res);
5816
            $force = $row['seriousgame_mode'];
5817
            if ($force == 1) {
5818
                $force = 0;
5819
            } elseif ($force == 0) {
5820
                $force = 1;
5821
            }
5822
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5823
			        WHERE iid = ".$this->get_id();
5824
            Database::query($sql);
5825
            $this->seriousgame_mode = $force;
5826
5827
            return $force;
5828
        }
5829
5830
        return -1;
5831
    }
5832
5833
    /**
5834
     * Updates the "scorm_debug" value that shows or hide the debug window.
5835
     *
5836
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5837
     */
5838
    public function update_scorm_debug()
5839
    {
5840
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5841
        $sql = "SELECT * FROM $lp_table
5842
                WHERE iid = ".$this->get_id();
5843
        $res = Database::query($sql);
5844
        if (Database::num_rows($res) > 0) {
5845
            $row = Database::fetch_array($res);
5846
            $force = $row['debug'];
5847
            if ($force == 1) {
5848
                $force = 0;
5849
            } elseif ($force == 0) {
5850
                $force = 1;
5851
            }
5852
            $sql = "UPDATE $lp_table SET debug = $force
5853
                    WHERE iid = ".$this->get_id();
5854
            Database::query($sql);
5855
            $this->scorm_debug = $force;
5856
5857
            return $force;
5858
        }
5859
5860
        return -1;
5861
    }
5862
5863
    /**
5864
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5865
     *
5866
     * @author Kevin Van Den Haute
5867
     *
5868
     * @param  array
5869
     */
5870
    public function tree_array($array)
5871
    {
5872
        $array = $this->sort_tree_array($array);
5873
        $this->create_tree_array($array);
5874
    }
5875
5876
    /**
5877
     * Creates an array with the elements of the learning path tree in it.
5878
     *
5879
     * @author Kevin Van Den Haute
5880
     *
5881
     * @param array $array
5882
     * @param int   $parent
5883
     * @param int   $depth
5884
     * @param array $tmp
5885
     */
5886
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5887
    {
5888
        if (is_array($array)) {
5889
            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...
5890
                if ($array[$i]['parent_item_id'] == $parent) {
5891
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5892
                        $tmp[] = $array[$i]['parent_item_id'];
5893
                        $depth++;
5894
                    }
5895
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5896
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5897
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5898
5899
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5900
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5901
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5902
                    $this->arrMenu[] = [
5903
                        'id' => $array[$i]['id'],
5904
                        'ref' => $ref,
5905
                        'item_type' => $array[$i]['item_type'],
5906
                        'title' => $array[$i]['title'],
5907
                        'title_raw' => $array[$i]['title_raw'],
5908
                        'path' => $path,
5909
                        'description' => $array[$i]['description'],
5910
                        'parent_item_id' => $array[$i]['parent_item_id'],
5911
                        'previous_item_id' => $array[$i]['previous_item_id'],
5912
                        'next_item_id' => $array[$i]['next_item_id'],
5913
                        'min_score' => $array[$i]['min_score'],
5914
                        'max_score' => $array[$i]['max_score'],
5915
                        'mastery_score' => $array[$i]['mastery_score'],
5916
                        'display_order' => $array[$i]['display_order'],
5917
                        'prerequisite' => $preq,
5918
                        'depth' => $depth,
5919
                        'audio' => $audio,
5920
                        'prerequisite_min_score' => $prerequisiteMinScore,
5921
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5922
                    ];
5923
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5924
                }
5925
            }
5926
        }
5927
    }
5928
5929
    /**
5930
     * Sorts a multi dimensional array by parent id and display order.
5931
     *
5932
     * @author Kevin Van Den Haute
5933
     *
5934
     * @param array $array (array with al the learning path items in it)
5935
     *
5936
     * @return array
5937
     */
5938
    public function sort_tree_array($array)
5939
    {
5940
        foreach ($array as $key => $row) {
5941
            $parent[$key] = $row['parent_item_id'];
5942
            $position[$key] = $row['display_order'];
5943
        }
5944
5945
        if (count($array) > 0) {
5946
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5947
        }
5948
5949
        return $array;
5950
    }
5951
5952
    /**
5953
     * Function that creates a html list of learning path items so that we can add audio files to them.
5954
     *
5955
     * @author Kevin Van Den Haute
5956
     *
5957
     * @return string
5958
     */
5959
    public function overview()
5960
    {
5961
        $return = '';
5962
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5963
5964
        // we need to start a form when we want to update all the mp3 files
5965
        if ($update_audio == 'true') {
5966
            $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">';
5967
        }
5968
        $return .= '<div id="message"></div>';
5969
        if (count($this->items) == 0) {
5970
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5971
        } else {
5972
            $return_audio = '<table class="data_table">';
5973
            $return_audio .= '<tr>';
5974
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5975
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5976
            $return_audio .= '</tr>';
5977
5978
            if ($update_audio != 'true') {
5979
                $return .= '<div class="col-md-12">';
5980
                $return .= self::return_new_tree($update_audio);
5981
                $return .= '</div>';
5982
                $return .= Display::div(
5983
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5984
                    ['style' => 'float:left; margin-top:15px;width:100%']
5985
                );
5986
            } else {
5987
                $return_audio .= self::return_new_tree($update_audio);
5988
                $return .= $return_audio.'</table>';
5989
            }
5990
5991
            // We need to close the form when we are updating the mp3 files.
5992
            if ($update_audio == 'true') {
5993
                $return .= '<div class="footer-audio">';
5994
                $return .= Display::button(
5995
                    'save_audio',
5996
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5997
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5998
                );
5999
                $return .= '</div>';
6000
            }
6001
        }
6002
6003
        // We need to close the form when we are updating the mp3 files.
6004
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6005
            $return .= '</form>';
6006
        }
6007
6008
        return $return;
6009
    }
6010
6011
    /**
6012
     * @param string $update_audio
6013
     *
6014
     * @return array
6015
     */
6016
    public function processBuildMenuElements($update_audio = 'false')
6017
    {
6018
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6019
        $arrLP = $this->getItemsForForm();
6020
6021
        $this->tree_array($arrLP);
6022
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6023
        unset($this->arrMenu);
6024
        $default_data = null;
6025
        $default_content = null;
6026
        $elements = [];
6027
        $return_audio = null;
6028
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6029
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6030
        $countItems = count($arrLP);
6031
6032
        $upIcon = Display::return_icon(
6033
            'up.png',
6034
            get_lang('Up'),
6035
            [],
6036
            ICON_SIZE_TINY
6037
        );
6038
6039
        $disableUpIcon = Display::return_icon(
6040
            'up_na.png',
6041
            get_lang('Up'),
6042
            [],
6043
            ICON_SIZE_TINY
6044
        );
6045
6046
        $downIcon = Display::return_icon(
6047
            'down.png',
6048
            get_lang('Down'),
6049
            [],
6050
            ICON_SIZE_TINY
6051
        );
6052
6053
        $disableDownIcon = Display::return_icon(
6054
            'down_na.png',
6055
            get_lang('Down'),
6056
            [],
6057
            ICON_SIZE_TINY
6058
        );
6059
6060
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6061
6062
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6063
        $plugin = null;
6064
        if ($pluginCalendar) {
6065
            $plugin = LearningCalendarPlugin::create();
6066
        }
6067
6068
        for ($i = 0; $i < $countItems; $i++) {
6069
            $parent_id = $arrLP[$i]['parent_item_id'];
6070
            $title = $arrLP[$i]['title'];
6071
            $title_cut = $arrLP[$i]['title_raw'];
6072
            if ($show === false) {
6073
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6074
            }
6075
            // Link for the documents
6076
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6077
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6078
                $title_cut = Display::url(
6079
                    $title_cut,
6080
                    $url,
6081
                    [
6082
                        'class' => 'ajax moved',
6083
                        'data-title' => $title,
6084
                        'title' => $title,
6085
                    ]
6086
                );
6087
            }
6088
6089
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6090
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6091
                Session::write('pathItem', $arrLP[$i]['path']);
6092
            }
6093
6094
            $oddClass = 'row_even';
6095
            if (($i % 2) == 0) {
6096
                $oddClass = 'row_odd';
6097
            }
6098
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6099
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6100
6101
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6102
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6103
            } else {
6104
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6105
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6106
                } else {
6107
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6108
                        $icon = Display::return_icon('certificate.png');
6109
                    } else {
6110
                        $icon = Display::return_icon('folder_document.gif');
6111
                    }
6112
                }
6113
            }
6114
6115
            // The audio column.
6116
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6117
            $audio = '';
6118
            if (!$update_audio || $update_audio != 'true') {
6119
                if (empty($arrLP[$i]['audio'])) {
6120
                    $audio .= '';
6121
                }
6122
            } else {
6123
                $types = self::getChapterTypes();
6124
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6125
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6126
                    if (!empty($arrLP[$i]['audio'])) {
6127
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6128
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6129
                    }
6130
                }
6131
            }
6132
6133
            $return_audio .= Display::span($icon.' '.$title).
6134
                Display::tag(
6135
                    'td',
6136
                    $audio,
6137
                    ['style' => '']
6138
                );
6139
            $return_audio .= '</td>';
6140
            $move_icon = '';
6141
            $move_item_icon = '';
6142
            $edit_icon = '';
6143
            $delete_icon = '';
6144
            $audio_icon = '';
6145
            $prerequisities_icon = '';
6146
            $forumIcon = '';
6147
            $previewIcon = '';
6148
            $pluginCalendarIcon = '';
6149
            $orderIcons = '';
6150
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6151
6152
            if ($is_allowed_to_edit) {
6153
                if (!$update_audio || $update_audio != 'true') {
6154
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6155
                        $move_icon .= '<a class="moved" href="#">';
6156
                        $move_icon .= Display::return_icon(
6157
                            'move_everywhere.png',
6158
                            get_lang('Move'),
6159
                            [],
6160
                            ICON_SIZE_TINY
6161
                        );
6162
                        $move_icon .= '</a>';
6163
                    }
6164
                }
6165
6166
                // No edit for this item types
6167
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6168
                    if ($arrLP[$i]['item_type'] != 'dir') {
6169
                        $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">';
6170
                        $edit_icon .= Display::return_icon(
6171
                            'edit.png',
6172
                            get_lang('LearnpathEditModule'),
6173
                            [],
6174
                            ICON_SIZE_TINY
6175
                        );
6176
                        $edit_icon .= '</a>';
6177
6178
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6179
                            $forumThread = null;
6180
                            if (isset($this->items[$arrLP[$i]['id']])) {
6181
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6182
                                    $this->course_int_id,
6183
                                    $this->lp_session_id
6184
                                );
6185
                            }
6186
                            if ($forumThread) {
6187
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6188
                                        'action' => 'dissociate_forum',
6189
                                        'id' => $arrLP[$i]['id'],
6190
                                        'lp_id' => $this->lp_id,
6191
                                    ]);
6192
                                $forumIcon = Display::url(
6193
                                    Display::return_icon(
6194
                                        'forum.png',
6195
                                        get_lang('DissociateForumToLPItem'),
6196
                                        [],
6197
                                        ICON_SIZE_TINY
6198
                                    ),
6199
                                    $forumIconUrl,
6200
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6201
                                );
6202
                            } else {
6203
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6204
                                        'action' => 'create_forum',
6205
                                        'id' => $arrLP[$i]['id'],
6206
                                        'lp_id' => $this->lp_id,
6207
                                    ]);
6208
                                $forumIcon = Display::url(
6209
                                    Display::return_icon(
6210
                                        'forum.png',
6211
                                        get_lang('AssociateForumToLPItem'),
6212
                                        [],
6213
                                        ICON_SIZE_TINY
6214
                                    ),
6215
                                    $forumIconUrl,
6216
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6217
                                );
6218
                            }
6219
                        }
6220
                    } else {
6221
                        $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">';
6222
                        $edit_icon .= Display::return_icon(
6223
                            'edit.png',
6224
                            get_lang('LearnpathEditModule'),
6225
                            [],
6226
                            ICON_SIZE_TINY
6227
                        );
6228
                        $edit_icon .= '</a>';
6229
                    }
6230
                } else {
6231
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6232
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6233
                        $edit_icon .= Display::return_icon(
6234
                            'edit.png',
6235
                            get_lang('Edit'),
6236
                            [],
6237
                            ICON_SIZE_TINY
6238
                        );
6239
                        $edit_icon .= '</a>';
6240
                    }
6241
                }
6242
6243
                if ($pluginCalendar) {
6244
                    $pluginLink = $pluginUrl.
6245
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6246
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6247
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6248
                    if ($itemInfo && $itemInfo['value'] == 1) {
6249
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6250
                    }
6251
                    $pluginCalendarIcon = Display::url(
6252
                        $iconCalendar,
6253
                        $pluginLink,
6254
                        ['class' => 'btn btn-default']
6255
                    );
6256
                }
6257
6258
                if ($arrLP[$i]['item_type'] != 'final_item') {
6259
                    $orderIcons = Display::url(
6260
                        $upIcon,
6261
                        'javascript:void(0)',
6262
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6263
                    );
6264
                    $orderIcons .= Display::url(
6265
                        $downIcon,
6266
                        'javascript:void(0)',
6267
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6268
                    );
6269
                }
6270
6271
                $delete_icon .= ' <a
6272
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6273
                    onclick="return confirmation(\''.addslashes($title).'\');"
6274
                    class="btn btn-default">';
6275
                $delete_icon .= Display::return_icon(
6276
                    'delete.png',
6277
                    get_lang('LearnpathDeleteModule'),
6278
                    [],
6279
                    ICON_SIZE_TINY
6280
                );
6281
                $delete_icon .= '</a>';
6282
6283
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6284
                $previewImage = Display::return_icon(
6285
                    'preview_view.png',
6286
                    get_lang('Preview'),
6287
                    [],
6288
                    ICON_SIZE_TINY
6289
                );
6290
6291
                switch ($arrLP[$i]['item_type']) {
6292
                    case TOOL_DOCUMENT:
6293
                    case TOOL_LP_FINAL_ITEM:
6294
                    case TOOL_READOUT_TEXT:
6295
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6296
                        $previewIcon = Display::url(
6297
                            $previewImage,
6298
                            $urlPreviewLink,
6299
                            [
6300
                                'target' => '_blank',
6301
                                'class' => 'btn btn-default',
6302
                                'data-title' => $arrLP[$i]['title'],
6303
                                'title' => $arrLP[$i]['title'],
6304
                            ]
6305
                        );
6306
                        break;
6307
                    case TOOL_THREAD:
6308
                    case TOOL_FORUM:
6309
                    case TOOL_QUIZ:
6310
                    case TOOL_STUDENTPUBLICATION:
6311
                    case TOOL_LP_FINAL_ITEM:
6312
                    case TOOL_LINK:
6313
                        $class = 'btn btn-default';
6314
                        $target = '_blank';
6315
                        $link = self::rl_get_resource_link_for_learnpath(
6316
                            $this->course_int_id,
6317
                            $this->lp_id,
6318
                            $arrLP[$i]['id'],
6319
                            0
6320
                        );
6321
                        $previewIcon = Display::url(
6322
                            $previewImage,
6323
                            $link,
6324
                            [
6325
                                'class' => $class,
6326
                                'data-title' => $arrLP[$i]['title'],
6327
                                'title' => $arrLP[$i]['title'],
6328
                                'target' => $target,
6329
                            ]
6330
                        );
6331
                        break;
6332
                    default:
6333
                        $previewIcon = Display::url(
6334
                            $previewImage,
6335
                            $url.'&action=view_item',
6336
                            ['class' => 'btn btn-default', 'target' => '_blank']
6337
                        );
6338
                        break;
6339
                }
6340
6341
                if ($arrLP[$i]['item_type'] != 'dir') {
6342
                    $prerequisities_icon = Display::url(
6343
                        Display::return_icon(
6344
                            'accept.png',
6345
                            get_lang('LearnpathPrerequisites'),
6346
                            [],
6347
                            ICON_SIZE_TINY
6348
                        ),
6349
                        $url.'&action=edit_item_prereq',
6350
                        ['class' => 'btn btn-default']
6351
                    );
6352
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6353
                        $move_item_icon = Display::url(
6354
                            Display::return_icon(
6355
                                'move.png',
6356
                                get_lang('Move'),
6357
                                [],
6358
                                ICON_SIZE_TINY
6359
                            ),
6360
                            $url.'&action=move_item',
6361
                            ['class' => 'btn btn-default']
6362
                        );
6363
                    }
6364
                    $audio_icon = Display::url(
6365
                        Display::return_icon(
6366
                            'audio.png',
6367
                            get_lang('UplUpload'),
6368
                            [],
6369
                            ICON_SIZE_TINY
6370
                        ),
6371
                        $url.'&action=add_audio',
6372
                        ['class' => 'btn btn-default']
6373
                    );
6374
                }
6375
            }
6376
            if ($update_audio != 'true') {
6377
                $row = $move_icon.' '.$icon.
6378
                    Display::span($title_cut).
6379
                    Display::tag(
6380
                        'div',
6381
                        "<div class=\"btn-group btn-group-xs\">
6382
                                    $previewIcon
6383
                                    $audio
6384
                                    $edit_icon
6385
                                    $pluginCalendarIcon
6386
                                    $forumIcon
6387
                                    $prerequisities_icon
6388
                                    $move_item_icon
6389
                                    $audio_icon
6390
                                    $orderIcons
6391
                                    $delete_icon
6392
                                </div>",
6393
                        ['class' => 'btn-toolbar button_actions']
6394
                    );
6395
            } else {
6396
                $row =
6397
                    Display::span($title.$icon).
6398
                    Display::span($audio, ['class' => 'button_actions']);
6399
            }
6400
6401
            $default_data[$arrLP[$i]['id']] = $row;
6402
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6403
6404
            if (empty($parent_id)) {
6405
                $elements[$arrLP[$i]['id']]['data'] = $row;
6406
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6407
            } else {
6408
                $parent_arrays = [];
6409
                if ($arrLP[$i]['depth'] > 1) {
6410
                    // Getting list of parents
6411
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6412
                        foreach ($arrLP as $item) {
6413
                            if ($item['id'] == $parent_id) {
6414
                                if ($item['parent_item_id'] == 0) {
6415
                                    $parent_id = $item['id'];
6416
                                    break;
6417
                                } else {
6418
                                    $parent_id = $item['parent_item_id'];
6419
                                    if (empty($parent_arrays)) {
6420
                                        $parent_arrays[] = intval($item['id']);
6421
                                    }
6422
                                    $parent_arrays[] = $parent_id;
6423
                                    break;
6424
                                }
6425
                            }
6426
                        }
6427
                    }
6428
                }
6429
6430
                if (!empty($parent_arrays)) {
6431
                    $parent_arrays = array_reverse($parent_arrays);
6432
                    $val = '$elements';
6433
                    $x = 0;
6434
                    foreach ($parent_arrays as $item) {
6435
                        if ($x != count($parent_arrays) - 1) {
6436
                            $val .= '["'.$item.'"]["children"]';
6437
                        } else {
6438
                            $val .= '["'.$item.'"]["children"]';
6439
                        }
6440
                        $x++;
6441
                    }
6442
                    $val .= "";
6443
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6444
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6445
                } else {
6446
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6447
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6448
                }
6449
            }
6450
        }
6451
6452
        return [
6453
            'elements' => $elements,
6454
            'default_data' => $default_data,
6455
            'default_content' => $default_content,
6456
            'return_audio' => $return_audio,
6457
        ];
6458
    }
6459
6460
    /**
6461
     * @param string $updateAudio true/false strings
6462
     *
6463
     * @return string
6464
     */
6465
    public function returnLpItemList($updateAudio)
6466
    {
6467
        $result = $this->processBuildMenuElements($updateAudio);
6468
6469
        $html = self::print_recursive(
6470
            $result['elements'],
6471
            $result['default_data'],
6472
            $result['default_content']
6473
        );
6474
6475
        if (!empty($html)) {
6476
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6477
        }
6478
6479
        return $html;
6480
    }
6481
6482
    /**
6483
     * @param string $update_audio
6484
     * @param bool   $drop_element_here
6485
     *
6486
     * @return string
6487
     */
6488
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6489
    {
6490
        $result = $this->processBuildMenuElements($update_audio);
6491
6492
        $list = '<ul id="lp_item_list">';
6493
        $tree = $this->print_recursive(
6494
            $result['elements'],
6495
            $result['default_data'],
6496
            $result['default_content']
6497
        );
6498
6499
        if (!empty($tree)) {
6500
            $list .= $tree;
6501
        } else {
6502
            if ($drop_element_here) {
6503
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6504
            }
6505
        }
6506
        $list .= '</ul>';
6507
6508
        $return = Display::panelCollapse(
6509
            $this->name,
6510
            $list,
6511
            'scorm-list',
6512
            null,
6513
            'scorm-list-accordion',
6514
            'scorm-list-collapse'
6515
        );
6516
6517
        if ($update_audio === 'true') {
6518
            $return = $result['return_audio'];
6519
        }
6520
6521
        return $return;
6522
    }
6523
6524
    /**
6525
     * @param array $elements
6526
     * @param array $default_data
6527
     * @param array $default_content
6528
     *
6529
     * @return string
6530
     */
6531
    public function print_recursive($elements, $default_data, $default_content)
6532
    {
6533
        $return = '';
6534
        foreach ($elements as $key => $item) {
6535
            if (isset($item['load_data']) || empty($item['data'])) {
6536
                $item['data'] = $default_data[$item['load_data']];
6537
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6538
            }
6539
            $sub_list = '';
6540
            if (isset($item['type']) && $item['type'] === 'dir') {
6541
                // empty value
6542
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6543
            }
6544
            if (empty($item['children'])) {
6545
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6546
                $active = null;
6547
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6548
                    $active = 'active';
6549
                }
6550
                $return .= Display::tag(
6551
                    'li',
6552
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6553
                    ['id' => $key, 'class' => 'record li_container']
6554
                );
6555
            } else {
6556
                // Sections
6557
                $data = '';
6558
                if (isset($item['children'])) {
6559
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6560
                }
6561
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6562
                $return .= Display::tag(
6563
                    'li',
6564
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6565
                    ['id' => $key, 'class' => 'record li_container']
6566
                );
6567
            }
6568
        }
6569
6570
        return $return;
6571
    }
6572
6573
    /**
6574
     * This function builds the action menu.
6575
     *
6576
     * @param bool $returnContent          Optional
6577
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6578
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6579
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6580
     *
6581
     * @return string
6582
     */
6583
    public function build_action_menu(
6584
        $returnContent = false,
6585
        $showRequirementButtons = true,
6586
        $isConfigPage = false,
6587
        $allowExpand = true
6588
    ) {
6589
        $actionsRight = '';
6590
        $actionsLeft = Display::url(
6591
            Display::return_icon(
6592
                'back.png',
6593
                get_lang('ReturnToLearningPaths'),
6594
                '',
6595
                ICON_SIZE_MEDIUM
6596
            ),
6597
            'lp_controller.php?'.api_get_cidreq()
6598
        );
6599
        $actionsLeft .= Display::url(
6600
            Display::return_icon(
6601
                'preview_view.png',
6602
                get_lang('Preview'),
6603
                '',
6604
                ICON_SIZE_MEDIUM
6605
            ),
6606
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6607
                'action' => 'view',
6608
                'lp_id' => $this->lp_id,
6609
                'isStudentView' => 'true',
6610
            ])
6611
        );
6612
6613
        $actionsLeft .= Display::url(
6614
            Display::return_icon(
6615
                'upload_audio.png',
6616
                get_lang('UpdateAllAudioFragments'),
6617
                '',
6618
                ICON_SIZE_MEDIUM
6619
            ),
6620
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6621
                'action' => 'admin_view',
6622
                'lp_id' => $this->lp_id,
6623
                'updateaudio' => 'true',
6624
            ])
6625
        );
6626
6627
        $subscriptionSettings = self::getSubscriptionSettings();
6628
6629
        $request = api_request_uri();
6630
        if (strpos($request, 'edit') === false) {
6631
            $actionsLeft .= Display::url(
6632
                Display::return_icon(
6633
                    'settings.png',
6634
                    get_lang('CourseSettings'),
6635
                    '',
6636
                    ICON_SIZE_MEDIUM
6637
                ),
6638
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6639
                    'action' => 'edit',
6640
                    'lp_id' => $this->lp_id,
6641
                ])
6642
            );
6643
        }
6644
6645
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6646
            $actionsLeft .= Display::url(
6647
                Display::return_icon(
6648
                    'edit.png',
6649
                    get_lang('Edit'),
6650
                    '',
6651
                    ICON_SIZE_MEDIUM
6652
                ),
6653
                'lp_controller.php?'.http_build_query([
6654
                    'action' => 'build',
6655
                    'lp_id' => $this->lp_id,
6656
                ]).'&'.api_get_cidreq()
6657
            );
6658
        }
6659
6660
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6661
            if ($this->subscribeUsers == 1 &&
6662
                $subscriptionSettings['allow_add_users_to_lp']) {
6663
                $actionsLeft .= Display::url(
6664
                    Display::return_icon(
6665
                        'user.png',
6666
                        get_lang('SubscribeUsersToLp'),
6667
                        '',
6668
                        ICON_SIZE_MEDIUM
6669
                    ),
6670
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6671
                );
6672
            }
6673
        }
6674
6675
        if ($allowExpand) {
6676
            $actionsLeft .= Display::url(
6677
                Display::return_icon(
6678
                    'expand.png',
6679
                    get_lang('Expand'),
6680
                    ['id' => 'expand'],
6681
                    ICON_SIZE_MEDIUM
6682
                ).
6683
                Display::return_icon(
6684
                    'contract.png',
6685
                    get_lang('Collapse'),
6686
                    ['id' => 'contract', 'class' => 'hide'],
6687
                    ICON_SIZE_MEDIUM
6688
                ),
6689
                '#',
6690
                ['role' => 'button', 'id' => 'hide_bar_template']
6691
            );
6692
        }
6693
6694
        if ($showRequirementButtons) {
6695
            $buttons = [
6696
                [
6697
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6698
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6699
                        'action' => 'set_previous_step_as_prerequisite',
6700
                        'lp_id' => $this->lp_id,
6701
                    ]),
6702
                ],
6703
                [
6704
                    'title' => get_lang('ClearAllPrerequisites'),
6705
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6706
                        'action' => 'clear_prerequisites',
6707
                        'lp_id' => $this->lp_id,
6708
                    ]),
6709
                ],
6710
            ];
6711
            $actionsRight = Display::groupButtonWithDropDown(
6712
                get_lang('PrerequisitesOptions'),
6713
                $buttons,
6714
                true
6715
            );
6716
        }
6717
6718
        $toolbar = Display::toolbarAction(
6719
            'actions-lp-controller',
6720
            [$actionsLeft, $actionsRight]
6721
        );
6722
6723
        if ($returnContent) {
6724
            return $toolbar;
6725
        }
6726
6727
        echo $toolbar;
6728
    }
6729
6730
    /**
6731
     * Creates the default learning path folder.
6732
     *
6733
     * @param array $course
6734
     * @param int   $creatorId
6735
     *
6736
     * @return bool
6737
     */
6738
    public static function generate_learning_path_folder($course, $creatorId = 0)
6739
    {
6740
        // Creating learning_path folder
6741
        $dir = '/learning_path';
6742
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6743
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6744
6745
        $folder = false;
6746
        if (!is_dir($filepath.'/'.$dir)) {
6747
            $folderData = create_unexisting_directory(
6748
                $course,
6749
                $creatorId,
6750
                0,
6751
                null,
6752
                0,
6753
                $filepath,
6754
                $dir,
6755
                get_lang('LearningPaths'),
6756
                0
6757
            );
6758
            if (!empty($folderData)) {
6759
                $folder = true;
6760
            }
6761
        } else {
6762
            $folder = true;
6763
        }
6764
6765
        return $folder;
6766
    }
6767
6768
    /**
6769
     * @param array  $course
6770
     * @param string $lp_name
6771
     * @param int    $creatorId
6772
     *
6773
     * @return array
6774
     */
6775
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6776
    {
6777
        $filepath = '';
6778
        $dir = '/learning_path/';
6779
6780
        if (empty($lp_name)) {
6781
            $lp_name = $this->name;
6782
        }
6783
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6784
        $folder = self::generate_learning_path_folder($course, $creatorId);
6785
6786
        // Limits title size
6787
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6788
        $dir = $dir.$title;
6789
6790
        // Creating LP folder
6791
        $documentId = null;
6792
        if ($folder) {
6793
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6794
            if (!is_dir($filepath.'/'.$dir)) {
6795
                $folderData = create_unexisting_directory(
6796
                    $course,
6797
                    $creatorId,
6798
                    0,
6799
                    0,
6800
                    0,
6801
                    $filepath,
6802
                    $dir,
6803
                    $lp_name
6804
                );
6805
                if (!empty($folderData)) {
6806
                    $folder = true;
6807
                }
6808
6809
                $documentId = $folderData['id'];
6810
            } else {
6811
                $folder = true;
6812
            }
6813
            $dir = $dir.'/';
6814
            if ($folder) {
6815
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6816
            }
6817
        }
6818
6819
        if (empty($documentId)) {
6820
            $dir = api_remove_trailing_slash($dir);
6821
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6822
        }
6823
6824
        $array = [
6825
            'dir' => $dir,
6826
            'filepath' => $filepath,
6827
            'folder' => $folder,
6828
            'id' => $documentId,
6829
        ];
6830
6831
        return $array;
6832
    }
6833
6834
    /**
6835
     * Create a new document //still needs some finetuning.
6836
     *
6837
     * @param array  $courseInfo
6838
     * @param string $content
6839
     * @param string $title
6840
     * @param string $extension
6841
     * @param int    $parentId
6842
     * @param int    $creatorId  creator id
6843
     *
6844
     * @return int
6845
     */
6846
    public function create_document(
6847
        $courseInfo,
6848
        $content = '',
6849
        $title = '',
6850
        $extension = 'html',
6851
        $parentId = 0,
6852
        $creatorId = 0
6853
    ) {
6854
        if (!empty($courseInfo)) {
6855
            $course_id = $courseInfo['real_id'];
6856
        } else {
6857
            $course_id = api_get_course_int_id();
6858
        }
6859
6860
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6861
        $sessionId = api_get_session_id();
6862
6863
        // Generates folder
6864
        $result = $this->generate_lp_folder($courseInfo);
6865
        $dir = $result['dir'];
6866
6867
        if (empty($parentId) || $parentId == '/') {
6868
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6869
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6870
6871
            if ($parentId === '/') {
6872
                $dir = '/';
6873
            }
6874
6875
            // Please, do not modify this dirname formatting.
6876
            if (strstr($dir, '..')) {
6877
                $dir = '/';
6878
            }
6879
6880
            if (!empty($dir[0]) && $dir[0] == '.') {
6881
                $dir = substr($dir, 1);
6882
            }
6883
            if (!empty($dir[0]) && $dir[0] != '/') {
6884
                $dir = '/'.$dir;
6885
            }
6886
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6887
                $dir .= '/';
6888
            }
6889
        } else {
6890
            $parentInfo = DocumentManager::get_document_data_by_id(
6891
                $parentId,
6892
                $courseInfo['code']
6893
            );
6894
            if (!empty($parentInfo)) {
6895
                $dir = $parentInfo['path'].'/';
6896
            }
6897
        }
6898
6899
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6900
        if (!is_dir($filepath)) {
6901
            $dir = '/';
6902
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6903
        }
6904
6905
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6906
        // is already escaped twice when it gets here.
6907
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6908
        if (!empty($title)) {
6909
            $title = api_replace_dangerous_char(stripslashes($title));
6910
        } else {
6911
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6912
        }
6913
6914
        $title = disable_dangerous_file($title);
6915
        $filename = $title;
6916
        $content = !empty($content) ? $content : $_POST['content_lp'];
6917
        $tmp_filename = $filename;
6918
6919
        $i = 0;
6920
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6921
            $tmp_filename = $filename.'_'.++$i;
6922
        }
6923
6924
        $filename = $tmp_filename.'.'.$extension;
6925
        if ($extension == 'html') {
6926
            $content = stripslashes($content);
6927
            $content = str_replace(
6928
                api_get_path(WEB_COURSE_PATH),
6929
                api_get_path(REL_PATH).'courses/',
6930
                $content
6931
            );
6932
6933
            // Change the path of mp3 to absolute.
6934
            // The first regexp deals with :// urls.
6935
            $content = preg_replace(
6936
                "|(flashvars=\"file=)([^:/]+)/|",
6937
                "$1".api_get_path(
6938
                    REL_COURSE_PATH
6939
                ).$courseInfo['path'].'/document/',
6940
                $content
6941
            );
6942
            // The second regexp deals with audio/ urls.
6943
            $content = preg_replace(
6944
                "|(flashvars=\"file=)([^/]+)/|",
6945
                "$1".api_get_path(
6946
                    REL_COURSE_PATH
6947
                ).$courseInfo['path'].'/document/$2/',
6948
                $content
6949
            );
6950
            // For flv player: To prevent edition problem with firefox,
6951
            // we have to use a strange tip (don't blame me please).
6952
            $content = str_replace(
6953
                '</body>',
6954
                '<style type="text/css">body{}</style></body>',
6955
                $content
6956
            );
6957
        }
6958
6959
        if (!file_exists($filepath.$filename)) {
6960
            if ($fp = @fopen($filepath.$filename, 'w')) {
6961
                fputs($fp, $content);
6962
                fclose($fp);
6963
6964
                $file_size = filesize($filepath.$filename);
6965
                $save_file_path = $dir.$filename;
6966
6967
                $document_id = add_document(
6968
                    $courseInfo,
6969
                    $save_file_path,
6970
                    'file',
6971
                    $file_size,
6972
                    $tmp_filename,
6973
                    '',
6974
                    0, //readonly
6975
                    true,
6976
                    null,
6977
                    $sessionId,
6978
                    $creatorId
6979
                );
6980
6981
                if ($document_id) {
6982
                    api_item_property_update(
6983
                        $courseInfo,
6984
                        TOOL_DOCUMENT,
6985
                        $document_id,
6986
                        'DocumentAdded',
6987
                        $creatorId,
6988
                        null,
6989
                        null,
6990
                        null,
6991
                        null,
6992
                        $sessionId
6993
                    );
6994
6995
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6996
                    $new_title = $originalTitle;
6997
6998
                    if ($new_comment || $new_title) {
6999
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7000
                        $ct = '';
7001
                        if ($new_comment) {
7002
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7003
                        }
7004
                        if ($new_title) {
7005
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7006
                        }
7007
7008
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7009
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7010
                        Database::query($sql);
7011
                    }
7012
                }
7013
7014
                return $document_id;
7015
            }
7016
        }
7017
    }
7018
7019
    /**
7020
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7021
     *
7022
     * @param array $_course array
7023
     */
7024
    public function edit_document($_course)
7025
    {
7026
        $course_id = api_get_course_int_id();
7027
        $urlAppend = api_get_configuration_value('url_append');
7028
        // Please, do not modify this dirname formatting.
7029
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7030
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7031
7032
        if (strstr($dir, '..')) {
7033
            $dir = '/';
7034
        }
7035
7036
        if (isset($dir[0]) && $dir[0] == '.') {
7037
            $dir = substr($dir, 1);
7038
        }
7039
7040
        if (isset($dir[0]) && $dir[0] != '/') {
7041
            $dir = '/'.$dir;
7042
        }
7043
7044
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7045
            $dir .= '/';
7046
        }
7047
7048
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7049
        if (!is_dir($filepath)) {
7050
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7051
        }
7052
7053
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7054
7055
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7056
            $document_id = (int) $_POST['path'];
7057
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7058
            if (empty($documentInfo)) {
7059
                // Try with iid
7060
                $table = Database::get_course_table(TABLE_DOCUMENT);
7061
                $sql = "SELECT id, path FROM $table
7062
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7063
                $res_doc = Database::query($sql);
7064
                $row = Database::fetch_array($res_doc);
7065
                if ($row) {
7066
                    $document_id = $row['id'];
7067
                    $documentPath = $row['path'];
7068
                }
7069
            } else {
7070
                $documentPath = $documentInfo['path'];
7071
            }
7072
7073
            $content = stripslashes($_POST['content_lp']);
7074
            $file = $filepath.$documentPath;
7075
7076
            if (!file_exists($file)) {
7077
                return false;
7078
            }
7079
7080
            if ($fp = @fopen($file, 'w')) {
7081
                $content = str_replace(
7082
                    api_get_path(WEB_COURSE_PATH),
7083
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7084
                    $content
7085
                );
7086
                // Change the path of mp3 to absolute.
7087
                // The first regexp deals with :// urls.
7088
                $content = preg_replace(
7089
                    "|(flashvars=\"file=)([^:/]+)/|",
7090
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7091
                    $content
7092
                );
7093
                // The second regexp deals with audio/ urls.
7094
                $content = preg_replace(
7095
                    "|(flashvars=\"file=)([^:/]+)/|",
7096
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7097
                    $content
7098
                );
7099
                fputs($fp, $content);
7100
                fclose($fp);
7101
7102
                $sql = "UPDATE $table_doc SET
7103
                            title='".Database::escape_string($_POST['title'])."'
7104
                        WHERE c_id = $course_id AND id = ".$document_id;
7105
                Database::query($sql);
7106
            }
7107
        }
7108
    }
7109
7110
    /**
7111
     * Displays the selected item, with a panel for manipulating the item.
7112
     *
7113
     * @param int    $item_id
7114
     * @param string $msg
7115
     * @param bool   $show_actions
7116
     *
7117
     * @return string
7118
     */
7119
    public function display_item($item_id, $msg = null, $show_actions = true)
7120
    {
7121
        $course_id = api_get_course_int_id();
7122
        $return = '';
7123
        if (is_numeric($item_id)) {
7124
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7125
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7126
                    WHERE lp.iid = ".intval($item_id);
7127
            $result = Database::query($sql);
7128
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7129
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7130
7131
                // Prevents wrong parent selection for document, see Bug#1251.
7132
                if ($row['item_type'] != 'dir') {
7133
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7134
                }
7135
7136
                if ($show_actions) {
7137
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7138
                }
7139
                $return .= '<div style="padding:10px;">';
7140
7141
                if ($msg != '') {
7142
                    $return .= $msg;
7143
                }
7144
7145
                $return .= '<h3>'.$row['title'].'</h3>';
7146
7147
                switch ($row['item_type']) {
7148
                    case TOOL_THREAD:
7149
                        $link = $this->rl_get_resource_link_for_learnpath(
7150
                            $course_id,
7151
                            $row['lp_id'],
7152
                            $item_id,
7153
                            0
7154
                        );
7155
                        $return .= Display::url(
7156
                            get_lang('GoToThread'),
7157
                            $link,
7158
                            ['class' => 'btn btn-primary']
7159
                        );
7160
                        break;
7161
                    case TOOL_FORUM:
7162
                        $return .= Display::url(
7163
                            get_lang('GoToForum'),
7164
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7165
                            ['class' => 'btn btn-primary']
7166
                        );
7167
                        break;
7168
                    case TOOL_QUIZ:
7169
                        if (!empty($row['path'])) {
7170
                            $exercise = new Exercise();
7171
                            $exercise->read($row['path']);
7172
                            $return .= $exercise->description.'<br />';
7173
                            $return .= Display::url(
7174
                                get_lang('GoToExercise'),
7175
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7176
                                ['class' => 'btn btn-primary']
7177
                            );
7178
                        }
7179
                        break;
7180
                    case TOOL_LP_FINAL_ITEM:
7181
                        $return .= $this->getSavedFinalItem();
7182
                        break;
7183
                    case TOOL_DOCUMENT:
7184
                    case TOOL_READOUT_TEXT:
7185
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7186
                        $sql_doc = "SELECT path FROM $tbl_doc
7187
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7188
                        $result = Database::query($sql_doc);
7189
                        $path_file = Database::result($result, 0, 0);
7190
                        $path_parts = pathinfo($path_file);
7191
                        // TODO: Correct the following naive comparisons.
7192
                        if (in_array($path_parts['extension'], [
7193
                            'html',
7194
                            'txt',
7195
                            'png',
7196
                            'jpg',
7197
                            'JPG',
7198
                            'jpeg',
7199
                            'JPEG',
7200
                            'gif',
7201
                            'swf',
7202
                            'pdf',
7203
                            'htm',
7204
                        ])) {
7205
                            $return .= $this->display_document($row['path'], true, true);
7206
                        }
7207
                        break;
7208
                    case TOOL_HOTPOTATOES:
7209
                        $return .= $this->display_document($row['path'], false, true);
7210
                        break;
7211
                }
7212
                $return .= '</div>';
7213
            }
7214
        }
7215
7216
        return $return;
7217
    }
7218
7219
    /**
7220
     * Shows the needed forms for editing a specific item.
7221
     *
7222
     * @param int $item_id
7223
     *
7224
     * @throws Exception
7225
     * @throws HTML_QuickForm_Error
7226
     *
7227
     * @return string
7228
     */
7229
    public function display_edit_item($item_id)
7230
    {
7231
        $course_id = api_get_course_int_id();
7232
        $return = '';
7233
        $item_id = (int) $item_id;
7234
7235
        if (empty($item_id)) {
7236
            return '';
7237
        }
7238
7239
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7240
        $sql = "SELECT * FROM $tbl_lp_item
7241
                WHERE iid = ".$item_id;
7242
        $res = Database::query($sql);
7243
        $row = Database::fetch_array($res);
7244
        switch ($row['item_type']) {
7245
            case 'dir':
7246
            case 'asset':
7247
            case 'sco':
7248
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7249
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7250
                    $return .= $this->display_item_form(
7251
                        $row['item_type'],
7252
                        get_lang('EditCurrentChapter').' :',
7253
                        'edit',
7254
                        $item_id,
7255
                        $row
7256
                    );
7257
                } else {
7258
                    $return .= $this->display_item_form(
7259
                        $row['item_type'],
7260
                        get_lang('EditCurrentChapter').' :',
7261
                        'edit_item',
7262
                        $item_id,
7263
                        $row
7264
                    );
7265
                }
7266
                break;
7267
            case TOOL_DOCUMENT:
7268
            case TOOL_READOUT_TEXT:
7269
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7270
                $sql = "SELECT lp.*, doc.path as dir
7271
                        FROM $tbl_lp_item as lp
7272
                        LEFT JOIN $tbl_doc as doc
7273
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7274
                        WHERE
7275
                            doc.c_id = $course_id AND
7276
                            lp.iid = ".$item_id;
7277
                $res_step = Database::query($sql);
7278
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7279
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7280
7281
                if ($row['item_type'] === TOOL_DOCUMENT) {
7282
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7283
                }
7284
7285
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7286
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7287
                }
7288
                break;
7289
            case TOOL_LINK:
7290
                $linkId = (int) $row['path'];
7291
                if (!empty($linkId)) {
7292
                    $table = Database::get_course_table(TABLE_LINK);
7293
                    $sql = 'SELECT url FROM '.$table.'
7294
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7295
                    $res_link = Database::query($sql);
7296
                    $row_link = Database::fetch_array($res_link);
7297
                    if (empty($row_link)) {
7298
                        // Try with id
7299
                        $sql = 'SELECT url FROM '.$table.'
7300
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7301
                        $res_link = Database::query($sql);
7302
                        $row_link = Database::fetch_array($res_link);
7303
                    }
7304
7305
                    if (is_array($row_link)) {
7306
                        $row['url'] = $row_link['url'];
7307
                    }
7308
                }
7309
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7310
                $return .= $this->display_link_form('edit', $item_id, $row);
7311
                break;
7312
            case TOOL_LP_FINAL_ITEM:
7313
                Session::write('finalItem', true);
7314
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7315
                $sql = "SELECT lp.*, doc.path as dir
7316
                        FROM $tbl_lp_item as lp
7317
                        LEFT JOIN $tbl_doc as doc
7318
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7319
                        WHERE
7320
                            doc.c_id = $course_id AND
7321
                            lp.iid = ".$item_id;
7322
                $res_step = Database::query($sql);
7323
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7324
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7325
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7326
                break;
7327
            case TOOL_QUIZ:
7328
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7329
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7330
                break;
7331
            case TOOL_HOTPOTATOES:
7332
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7333
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7334
                break;
7335
            case TOOL_STUDENTPUBLICATION:
7336
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7337
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7338
                break;
7339
            case TOOL_FORUM:
7340
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7341
                $return .= $this->display_forum_form('edit', $item_id, $row);
7342
                break;
7343
            case TOOL_THREAD:
7344
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7345
                $return .= $this->display_thread_form('edit', $item_id, $row);
7346
                break;
7347
        }
7348
7349
        return $return;
7350
    }
7351
7352
    /**
7353
     * Function that displays a list with al the resources that
7354
     * could be added to the learning path.
7355
     *
7356
     * @throws Exception
7357
     * @throws HTML_QuickForm_Error
7358
     *
7359
     * @return bool
7360
     */
7361
    public function display_resources()
7362
    {
7363
        $course_code = api_get_course_id();
7364
7365
        // Get all the docs.
7366
        $documents = $this->get_documents(true);
7367
7368
        // Get all the exercises.
7369
        $exercises = $this->get_exercises();
7370
7371
        // Get all the links.
7372
        $links = $this->get_links();
7373
7374
        // Get all the student publications.
7375
        $works = $this->get_student_publications();
7376
7377
        // Get all the forums.
7378
        $forums = $this->get_forums(null, $course_code);
7379
7380
        // Get the final item form (see BT#11048) .
7381
        $finish = $this->getFinalItemForm();
7382
7383
        $headers = [
7384
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7385
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7386
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7387
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7388
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7389
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7390
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7391
        ];
7392
7393
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7394
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7395
7396
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7397
7398
        echo Display::tabs(
7399
            $headers,
7400
            [
7401
                $documents,
7402
                $exercises,
7403
                $links,
7404
                $works,
7405
                $forums,
7406
                $dir,
7407
                $finish,
7408
            ],
7409
            'resource_tab',
7410
            [],
7411
            [],
7412
            $selected
7413
        );
7414
7415
        return true;
7416
    }
7417
7418
    /**
7419
     * Returns the extension of a document.
7420
     *
7421
     * @param string $filename
7422
     *
7423
     * @return string Extension (part after the last dot)
7424
     */
7425
    public function get_extension($filename)
7426
    {
7427
        $explode = explode('.', $filename);
7428
7429
        return $explode[count($explode) - 1];
7430
    }
7431
7432
    /**
7433
     * Displays a document by id.
7434
     *
7435
     * @param int  $id
7436
     * @param bool $show_title
7437
     * @param bool $iframe
7438
     * @param bool $edit_link
7439
     *
7440
     * @return string
7441
     */
7442
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7443
    {
7444
        $_course = api_get_course_info();
7445
        $course_id = api_get_course_int_id();
7446
        $id = (int) $id;
7447
        $return = '';
7448
        $table = Database::get_course_table(TABLE_DOCUMENT);
7449
        $sql_doc = "SELECT * FROM $table
7450
                    WHERE c_id = $course_id AND iid = $id";
7451
        $res_doc = Database::query($sql_doc);
7452
        $row_doc = Database::fetch_array($res_doc);
7453
7454
        // TODO: Add a path filter.
7455
        if ($iframe) {
7456
            $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>';
7457
        } else {
7458
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7459
        }
7460
7461
        return $return;
7462
    }
7463
7464
    /**
7465
     * Return HTML form to add/edit a quiz.
7466
     *
7467
     * @param string $action     Action (add/edit)
7468
     * @param int    $id         Item ID if already exists
7469
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7470
     *
7471
     * @throws Exception
7472
     *
7473
     * @return string HTML form
7474
     */
7475
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7476
    {
7477
        $course_id = api_get_course_int_id();
7478
        $id = (int) $id;
7479
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7480
7481
        if ($id != 0 && is_array($extra_info)) {
7482
            $item_title = $extra_info['title'];
7483
            $item_description = $extra_info['description'];
7484
        } elseif (is_numeric($extra_info)) {
7485
            $sql = "SELECT title, description
7486
                    FROM $tbl_quiz
7487
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7488
7489
            $result = Database::query($sql);
7490
            $row = Database::fetch_array($result);
7491
            $item_title = $row['title'];
7492
            $item_description = $row['description'];
7493
        } else {
7494
            $item_title = '';
7495
            $item_description = '';
7496
        }
7497
        $item_title = Security::remove_XSS($item_title);
7498
        $item_description = Security::remove_XSS($item_description);
7499
7500
        $parent = 0;
7501
        if ($id != 0 && is_array($extra_info)) {
7502
            $parent = $extra_info['parent_item_id'];
7503
        }
7504
7505
        $arrLP = $this->getItemsForForm();
7506
        $this->tree_array($arrLP);
7507
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7508
        unset($this->arrMenu);
7509
7510
        $form = new FormValidator(
7511
            'quiz_form',
7512
            'POST',
7513
            $this->getCurrentBuildingModeURL()
7514
        );
7515
        $defaults = [];
7516
7517
        if ($action === 'add') {
7518
            $legend = get_lang('CreateTheExercise');
7519
        } elseif ($action === 'move') {
7520
            $legend = get_lang('MoveTheCurrentExercise');
7521
        } else {
7522
            $legend = get_lang('EditCurrentExecice');
7523
        }
7524
7525
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7526
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7527
        }
7528
7529
        $form->addHeader($legend);
7530
7531
        if ($action != 'move') {
7532
            $this->setItemTitle($form);
7533
            $defaults['title'] = $item_title;
7534
        }
7535
7536
        // Select for Parent item, root or chapter
7537
        $selectParent = $form->addSelect(
7538
            'parent',
7539
            get_lang('Parent'),
7540
            [],
7541
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7542
        );
7543
        $selectParent->addOption($this->name, 0);
7544
7545
        $arrHide = [
7546
            $id,
7547
        ];
7548
        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...
7549
            if ($action != 'add') {
7550
                if (
7551
                    ($arrLP[$i]['item_type'] == 'dir') &&
7552
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7553
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7554
                ) {
7555
                    $selectParent->addOption(
7556
                        $arrLP[$i]['title'],
7557
                        $arrLP[$i]['id'],
7558
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7559
                    );
7560
7561
                    if ($parent == $arrLP[$i]['id']) {
7562
                        $selectParent->setSelected($arrLP[$i]['id']);
7563
                    }
7564
                } else {
7565
                    $arrHide[] = $arrLP[$i]['id'];
7566
                }
7567
            } else {
7568
                if ($arrLP[$i]['item_type'] == 'dir') {
7569
                    $selectParent->addOption(
7570
                        $arrLP[$i]['title'],
7571
                        $arrLP[$i]['id'],
7572
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7573
                    );
7574
7575
                    if ($parent == $arrLP[$i]['id']) {
7576
                        $selectParent->setSelected($arrLP[$i]['id']);
7577
                    }
7578
                }
7579
            }
7580
        }
7581
7582
        if (is_array($arrLP)) {
7583
            reset($arrLP);
7584
        }
7585
7586
        $selectPrevious = $form->addSelect(
7587
            'previous',
7588
            get_lang('Position'),
7589
            [],
7590
            ['id' => 'previous']
7591
        );
7592
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7593
7594
        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...
7595
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7596
                $arrLP[$i]['id'] != $id
7597
            ) {
7598
                $selectPrevious->addOption(
7599
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7600
                    $arrLP[$i]['id']
7601
                );
7602
7603
                if (is_array($extra_info)) {
7604
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7605
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7606
                    }
7607
                } elseif ($action == 'add') {
7608
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7609
                }
7610
            }
7611
        }
7612
7613
        if ($action != 'move') {
7614
            $arrHide = [];
7615
            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...
7616
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7617
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7618
                }
7619
            }
7620
        }
7621
7622
        if ($action === 'add') {
7623
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7624
        } else {
7625
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7626
        }
7627
7628
        if ($action === 'move') {
7629
            $form->addHidden('title', $item_title);
7630
            $form->addHidden('description', $item_description);
7631
        }
7632
7633
        if (is_numeric($extra_info)) {
7634
            $form->addHidden('path', $extra_info);
7635
        } elseif (is_array($extra_info)) {
7636
            $form->addHidden('path', $extra_info['path']);
7637
        }
7638
7639
        $form->addHidden('type', TOOL_QUIZ);
7640
        $form->addHidden('post_time', time());
7641
        $form->setDefaults($defaults);
7642
7643
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7644
    }
7645
7646
    /**
7647
     * Addition of Hotpotatoes tests.
7648
     *
7649
     * @param string $action
7650
     * @param int    $id         Internal ID of the item
7651
     * @param string $extra_info
7652
     *
7653
     * @return string HTML structure to display the hotpotatoes addition formular
7654
     */
7655
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7656
    {
7657
        $course_id = api_get_course_int_id();
7658
        $uploadPath = DIR_HOTPOTATOES;
7659
7660
        if ($id != 0 && is_array($extra_info)) {
7661
            $item_title = stripslashes($extra_info['title']);
7662
            $item_description = stripslashes($extra_info['description']);
7663
        } elseif (is_numeric($extra_info)) {
7664
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7665
7666
            $sql = "SELECT * FROM $TBL_DOCUMENT
7667
                    WHERE
7668
                        c_id = $course_id AND
7669
                        path LIKE '".$uploadPath."/%/%htm%' AND
7670
                        iid = ".(int) $extra_info."
7671
                    ORDER BY iid ASC";
7672
7673
            $res_hot = Database::query($sql);
7674
            $row = Database::fetch_array($res_hot);
7675
7676
            $item_title = $row['title'];
7677
            $item_description = $row['description'];
7678
7679
            if (!empty($row['comment'])) {
7680
                $item_title = $row['comment'];
7681
            }
7682
        } else {
7683
            $item_title = '';
7684
            $item_description = '';
7685
        }
7686
7687
        $parent = 0;
7688
        if ($id != 0 && is_array($extra_info)) {
7689
            $parent = $extra_info['parent_item_id'];
7690
        }
7691
7692
        $arrLP = $this->getItemsForForm();
7693
        $legend = '<legend>';
7694
        if ($action == 'add') {
7695
            $legend .= get_lang('CreateTheExercise');
7696
        } elseif ($action == 'move') {
7697
            $legend .= get_lang('MoveTheCurrentExercise');
7698
        } else {
7699
            $legend .= get_lang('EditCurrentExecice');
7700
        }
7701
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7702
            $legend .= Display:: return_message(
7703
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7704
            );
7705
        }
7706
        $legend .= '</legend>';
7707
7708
        $return = '<form method="POST">';
7709
        $return .= $legend;
7710
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7711
        $return .= '<tr>';
7712
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7713
        $return .= '<td class="input">';
7714
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7715
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7716
        $arrHide = [$id];
7717
7718
        if (count($arrLP) > 0) {
7719
            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...
7720
                if ($action != 'add') {
7721
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7722
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7723
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7724
                    ) {
7725
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7726
                    } else {
7727
                        $arrHide[] = $arrLP[$i]['id'];
7728
                    }
7729
                } else {
7730
                    if ($arrLP[$i]['item_type'] == 'dir') {
7731
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7732
                    }
7733
                }
7734
            }
7735
            reset($arrLP);
7736
        }
7737
7738
        $return .= '</select>';
7739
        $return .= '</td>';
7740
        $return .= '</tr>';
7741
        $return .= '<tr>';
7742
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7743
        $return .= '<td class="input">';
7744
        $return .= '<select id="previous" name="previous" size="1">';
7745
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7746
7747
        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...
7748
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7749
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7750
                    $selected = 'selected="selected" ';
7751
                } elseif ($action == 'add') {
7752
                    $selected = 'selected="selected" ';
7753
                } else {
7754
                    $selected = '';
7755
                }
7756
7757
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7758
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7759
            }
7760
        }
7761
7762
        $return .= '</select>';
7763
        $return .= '</td>';
7764
        $return .= '</tr>';
7765
7766
        if ($action != 'move') {
7767
            $return .= '<tr>';
7768
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7769
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7770
            $return .= '</tr>';
7771
            $id_prerequisite = 0;
7772
            if (is_array($arrLP) && count($arrLP) > 0) {
7773
                foreach ($arrLP as $key => $value) {
7774
                    if ($value['id'] == $id) {
7775
                        $id_prerequisite = $value['prerequisite'];
7776
                        break;
7777
                    }
7778
                }
7779
7780
                $arrHide = [];
7781
                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...
7782
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7783
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7784
                    }
7785
                }
7786
            }
7787
        }
7788
7789
        $return .= '<tr>';
7790
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7791
            get_lang('SaveHotpotatoes').'</button></td>';
7792
        $return .= '</tr>';
7793
        $return .= '</table>';
7794
7795
        if ($action == 'move') {
7796
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7797
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7798
        }
7799
7800
        if (is_numeric($extra_info)) {
7801
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7802
        } elseif (is_array($extra_info)) {
7803
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7804
        }
7805
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7806
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7807
        $return .= '</form>';
7808
7809
        return $return;
7810
    }
7811
7812
    /**
7813
     * Return the form to display the forum edit/add option.
7814
     *
7815
     * @param string $action
7816
     * @param int    $id         ID of the lp_item if already exists
7817
     * @param string $extra_info
7818
     *
7819
     * @throws Exception
7820
     *
7821
     * @return string HTML form
7822
     */
7823
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7824
    {
7825
        $course_id = api_get_course_int_id();
7826
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7827
7828
        $item_title = '';
7829
        $item_description = '';
7830
7831
        if ($id != 0 && is_array($extra_info)) {
7832
            $item_title = stripslashes($extra_info['title']);
7833
        } elseif (is_numeric($extra_info)) {
7834
            $sql = "SELECT forum_title as title, forum_comment as comment
7835
                    FROM $tbl_forum
7836
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7837
7838
            $result = Database::query($sql);
7839
            $row = Database::fetch_array($result);
7840
7841
            $item_title = $row['title'];
7842
            $item_description = $row['comment'];
7843
        }
7844
        $parent = 0;
7845
        if ($id != 0 && is_array($extra_info)) {
7846
            $parent = $extra_info['parent_item_id'];
7847
        }
7848
        $arrLP = $this->getItemsForForm();
7849
        $this->tree_array($arrLP);
7850
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7851
        unset($this->arrMenu);
7852
7853
        if ($action == 'add') {
7854
            $legend = get_lang('CreateTheForum');
7855
        } elseif ($action == 'move') {
7856
            $legend = get_lang('MoveTheCurrentForum');
7857
        } else {
7858
            $legend = get_lang('EditCurrentForum');
7859
        }
7860
7861
        $form = new FormValidator(
7862
            'forum_form',
7863
            'POST',
7864
            $this->getCurrentBuildingModeURL()
7865
        );
7866
        $defaults = [];
7867
7868
        $form->addHeader($legend);
7869
7870
        if ($action != 'move') {
7871
            $this->setItemTitle($form);
7872
            $defaults['title'] = $item_title;
7873
        }
7874
7875
        $selectParent = $form->addSelect(
7876
            'parent',
7877
            get_lang('Parent'),
7878
            [],
7879
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7880
        );
7881
        $selectParent->addOption($this->name, 0);
7882
        $arrHide = [
7883
            $id,
7884
        ];
7885
        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...
7886
            if ($action != 'add') {
7887
                if ($arrLP[$i]['item_type'] == 'dir' &&
7888
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7889
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7890
                ) {
7891
                    $selectParent->addOption(
7892
                        $arrLP[$i]['title'],
7893
                        $arrLP[$i]['id'],
7894
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7895
                    );
7896
7897
                    if ($parent == $arrLP[$i]['id']) {
7898
                        $selectParent->setSelected($arrLP[$i]['id']);
7899
                    }
7900
                } else {
7901
                    $arrHide[] = $arrLP[$i]['id'];
7902
                }
7903
            } else {
7904
                if ($arrLP[$i]['item_type'] == 'dir') {
7905
                    $selectParent->addOption(
7906
                        $arrLP[$i]['title'],
7907
                        $arrLP[$i]['id'],
7908
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7909
                    );
7910
7911
                    if ($parent == $arrLP[$i]['id']) {
7912
                        $selectParent->setSelected($arrLP[$i]['id']);
7913
                    }
7914
                }
7915
            }
7916
        }
7917
7918
        if (is_array($arrLP)) {
7919
            reset($arrLP);
7920
        }
7921
7922
        $selectPrevious = $form->addSelect(
7923
            'previous',
7924
            get_lang('Position'),
7925
            [],
7926
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7927
        );
7928
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7929
7930
        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...
7931
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7932
                $arrLP[$i]['id'] != $id
7933
            ) {
7934
                $selectPrevious->addOption(
7935
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7936
                    $arrLP[$i]['id']
7937
                );
7938
7939
                if (isset($extra_info['previous_item_id']) &&
7940
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7941
                ) {
7942
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7943
                } elseif ($action == 'add') {
7944
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7945
                }
7946
            }
7947
        }
7948
7949
        if ($action != 'move') {
7950
            $id_prerequisite = 0;
7951
            if (is_array($arrLP)) {
7952
                foreach ($arrLP as $key => $value) {
7953
                    if ($value['id'] == $id) {
7954
                        $id_prerequisite = $value['prerequisite'];
7955
                        break;
7956
                    }
7957
                }
7958
            }
7959
7960
            $arrHide = [];
7961
            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...
7962
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7963
                    if (isset($extra_info['previous_item_id']) &&
7964
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7965
                    ) {
7966
                        $s_selected_position = $arrLP[$i]['id'];
7967
                    } elseif ($action == 'add') {
7968
                        $s_selected_position = 0;
7969
                    }
7970
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7971
                }
7972
            }
7973
        }
7974
7975
        if ($action == 'add') {
7976
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
7977
        } else {
7978
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
7979
        }
7980
7981
        if ($action == 'move') {
7982
            $form->addHidden('title', $item_title);
7983
            $form->addHidden('description', $item_description);
7984
        }
7985
7986
        if (is_numeric($extra_info)) {
7987
            $form->addHidden('path', $extra_info);
7988
        } elseif (is_array($extra_info)) {
7989
            $form->addHidden('path', $extra_info['path']);
7990
        }
7991
        $form->addHidden('type', TOOL_FORUM);
7992
        $form->addHidden('post_time', time());
7993
        $form->setDefaults($defaults);
7994
7995
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7996
    }
7997
7998
    /**
7999
     * Return HTML form to add/edit forum threads.
8000
     *
8001
     * @param string $action
8002
     * @param int    $id         Item ID if already exists in learning path
8003
     * @param string $extra_info
8004
     *
8005
     * @throws Exception
8006
     *
8007
     * @return string HTML form
8008
     */
8009
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8010
    {
8011
        $course_id = api_get_course_int_id();
8012
        if (empty($course_id)) {
8013
            return null;
8014
        }
8015
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8016
8017
        $item_title = '';
8018
        $item_description = '';
8019
        if ($id != 0 && is_array($extra_info)) {
8020
            $item_title = stripslashes($extra_info['title']);
8021
        } elseif (is_numeric($extra_info)) {
8022
            $sql = "SELECT thread_title as title FROM $tbl_forum
8023
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8024
8025
            $result = Database::query($sql);
8026
            $row = Database::fetch_array($result);
8027
8028
            $item_title = $row['title'];
8029
            $item_description = '';
8030
        }
8031
8032
        $parent = 0;
8033
        if ($id != 0 && is_array($extra_info)) {
8034
            $parent = $extra_info['parent_item_id'];
8035
        }
8036
8037
        $arrLP = $this->getItemsForForm();
8038
        $this->tree_array($arrLP);
8039
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8040
        unset($this->arrMenu);
8041
8042
        $form = new FormValidator(
8043
            'thread_form',
8044
            'POST',
8045
            $this->getCurrentBuildingModeURL()
8046
        );
8047
        $defaults = [];
8048
8049
        if ($action == 'add') {
8050
            $legend = get_lang('CreateTheForum');
8051
        } elseif ($action == 'move') {
8052
            $legend = get_lang('MoveTheCurrentForum');
8053
        } else {
8054
            $legend = get_lang('EditCurrentForum');
8055
        }
8056
8057
        $form->addHeader($legend);
8058
        $selectParent = $form->addSelect(
8059
            'parent',
8060
            get_lang('Parent'),
8061
            [],
8062
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8063
        );
8064
        $selectParent->addOption($this->name, 0);
8065
8066
        $arrHide = [
8067
            $id,
8068
        ];
8069
8070
        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...
8071
            if ($action != 'add') {
8072
                if (
8073
                    ($arrLP[$i]['item_type'] == 'dir') &&
8074
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8075
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8076
                ) {
8077
                    $selectParent->addOption(
8078
                        $arrLP[$i]['title'],
8079
                        $arrLP[$i]['id'],
8080
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8081
                    );
8082
8083
                    if ($parent == $arrLP[$i]['id']) {
8084
                        $selectParent->setSelected($arrLP[$i]['id']);
8085
                    }
8086
                } else {
8087
                    $arrHide[] = $arrLP[$i]['id'];
8088
                }
8089
            } else {
8090
                if ($arrLP[$i]['item_type'] == 'dir') {
8091
                    $selectParent->addOption(
8092
                        $arrLP[$i]['title'],
8093
                        $arrLP[$i]['id'],
8094
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8095
                    );
8096
8097
                    if ($parent == $arrLP[$i]['id']) {
8098
                        $selectParent->setSelected($arrLP[$i]['id']);
8099
                    }
8100
                }
8101
            }
8102
        }
8103
8104
        if ($arrLP != null) {
8105
            reset($arrLP);
8106
        }
8107
8108
        $selectPrevious = $form->addSelect(
8109
            'previous',
8110
            get_lang('Position'),
8111
            [],
8112
            ['id' => 'previous']
8113
        );
8114
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8115
8116
        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...
8117
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8118
                $selectPrevious->addOption(
8119
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8120
                    $arrLP[$i]['id']
8121
                );
8122
8123
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8124
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8125
                } elseif ($action == 'add') {
8126
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8127
                }
8128
            }
8129
        }
8130
8131
        if ($action != 'move') {
8132
            $this->setItemTitle($form);
8133
            $defaults['title'] = $item_title;
8134
8135
            $id_prerequisite = 0;
8136
            if ($arrLP != null) {
8137
                foreach ($arrLP as $key => $value) {
8138
                    if ($value['id'] == $id) {
8139
                        $id_prerequisite = $value['prerequisite'];
8140
                        break;
8141
                    }
8142
                }
8143
            }
8144
8145
            $arrHide = [];
8146
            $s_selected_position = 0;
8147
            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...
8148
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8149
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8150
                        $s_selected_position = $arrLP[$i]['id'];
8151
                    } elseif ($action == 'add') {
8152
                        $s_selected_position = 0;
8153
                    }
8154
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8155
                }
8156
            }
8157
8158
            $selectPrerequisites = $form->addSelect(
8159
                'prerequisites',
8160
                get_lang('LearnpathPrerequisites'),
8161
                [],
8162
                ['id' => 'prerequisites']
8163
            );
8164
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8165
8166
            foreach ($arrHide as $key => $value) {
8167
                $selectPrerequisites->addOption($value['value'], $key);
8168
8169
                if ($key == $s_selected_position && $action == 'add') {
8170
                    $selectPrerequisites->setSelected($key);
8171
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8172
                    $selectPrerequisites->setSelected($key);
8173
                }
8174
            }
8175
        }
8176
8177
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8178
8179
        if ($action == 'move') {
8180
            $form->addHidden('title', $item_title);
8181
            $form->addHidden('description', $item_description);
8182
        }
8183
8184
        if (is_numeric($extra_info)) {
8185
            $form->addHidden('path', $extra_info);
8186
        } elseif (is_array($extra_info)) {
8187
            $form->addHidden('path', $extra_info['path']);
8188
        }
8189
8190
        $form->addHidden('type', TOOL_THREAD);
8191
        $form->addHidden('post_time', time());
8192
        $form->setDefaults($defaults);
8193
8194
        return $form->returnForm();
8195
    }
8196
8197
    /**
8198
     * Return the HTML form to display an item (generally a dir item).
8199
     *
8200
     * @param string $item_type
8201
     * @param string $title
8202
     * @param string $action
8203
     * @param int    $id
8204
     * @param string $extra_info
8205
     *
8206
     * @throws Exception
8207
     * @throws HTML_QuickForm_Error
8208
     *
8209
     * @return string HTML form
8210
     */
8211
    public function display_item_form(
8212
        $item_type,
8213
        $title = '',
8214
        $action = 'add_item',
8215
        $id = 0,
8216
        $extra_info = 'new'
8217
    ) {
8218
        $_course = api_get_course_info();
8219
8220
        global $charset;
8221
8222
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8223
        $item_title = '';
8224
        $item_description = '';
8225
        $item_path_fck = '';
8226
8227
        if ($id != 0 && is_array($extra_info)) {
8228
            $item_title = $extra_info['title'];
8229
            $item_description = $extra_info['description'];
8230
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8231
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8232
        }
8233
        $parent = 0;
8234
        if ($id != 0 && is_array($extra_info)) {
8235
            $parent = $extra_info['parent_item_id'];
8236
        }
8237
8238
        $id = (int) $id;
8239
        $sql = "SELECT * FROM $tbl_lp_item
8240
                WHERE
8241
                    lp_id = ".$this->lp_id." AND
8242
                    iid != $id";
8243
8244
        if ($item_type == 'dir') {
8245
            $sql .= " AND parent_item_id = 0";
8246
        }
8247
8248
        $result = Database::query($sql);
8249
        $arrLP = [];
8250
        while ($row = Database::fetch_array($result)) {
8251
            $arrLP[] = [
8252
                'id' => $row['iid'],
8253
                'item_type' => $row['item_type'],
8254
                'title' => $this->cleanItemTitle($row['title']),
8255
                'title_raw' => $row['title'],
8256
                'path' => $row['path'],
8257
                'description' => $row['description'],
8258
                'parent_item_id' => $row['parent_item_id'],
8259
                'previous_item_id' => $row['previous_item_id'],
8260
                'next_item_id' => $row['next_item_id'],
8261
                'max_score' => $row['max_score'],
8262
                'min_score' => $row['min_score'],
8263
                'mastery_score' => $row['mastery_score'],
8264
                'prerequisite' => $row['prerequisite'],
8265
                'display_order' => $row['display_order'],
8266
            ];
8267
        }
8268
8269
        $this->tree_array($arrLP);
8270
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8271
        unset($this->arrMenu);
8272
8273
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8274
8275
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8276
        $defaults['title'] = api_html_entity_decode(
8277
            $item_title,
8278
            ENT_QUOTES,
8279
            $charset
8280
        );
8281
        $defaults['description'] = $item_description;
8282
8283
        $form->addHeader($title);
8284
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8285
        $arrHide[0]['padding'] = 20;
8286
        $charset = api_get_system_encoding();
8287
        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...
8288
            if ($action != 'add') {
8289
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8290
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8291
                ) {
8292
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8293
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8294
                    if ($parent == $arrLP[$i]['id']) {
8295
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8296
                    }
8297
                }
8298
            } else {
8299
                if ($arrLP[$i]['item_type'] === 'dir') {
8300
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8301
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8302
                    if ($parent == $arrLP[$i]['id']) {
8303
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8304
                    }
8305
                }
8306
            }
8307
        }
8308
8309
        if ($action != 'move') {
8310
            $this->setItemTitle($form);
8311
        } else {
8312
            $form->addElement('hidden', 'title');
8313
        }
8314
8315
        $parentSelect = $form->addElement(
8316
            'select',
8317
            'parent',
8318
            get_lang('Parent'),
8319
            '',
8320
            [
8321
                'id' => 'idParent',
8322
                'onchange' => 'javascript: load_cbo(this.value);',
8323
            ]
8324
        );
8325
8326
        foreach ($arrHide as $key => $value) {
8327
            $parentSelect->addOption(
8328
                $value['value'],
8329
                $key,
8330
                'style="padding-left:'.$value['padding'].'px;"'
8331
            );
8332
            $lastPosition = $key;
8333
        }
8334
8335
        if (!empty($s_selected_parent)) {
8336
            $parentSelect->setSelected($s_selected_parent);
8337
        }
8338
8339
        if (is_array($arrLP)) {
8340
            reset($arrLP);
8341
        }
8342
8343
        $arrHide = [];
8344
        // POSITION
8345
        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...
8346
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8347
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8348
                //this is the same!
8349
                if (isset($extra_info['previous_item_id']) &&
8350
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8351
                ) {
8352
                    $s_selected_position = $arrLP[$i]['id'];
8353
                } elseif ($action == 'add') {
8354
                    $s_selected_position = $arrLP[$i]['id'];
8355
                }
8356
8357
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8358
            }
8359
        }
8360
8361
        $position = $form->addElement(
8362
            'select',
8363
            'previous',
8364
            get_lang('Position'),
8365
            '',
8366
            ['id' => 'previous']
8367
        );
8368
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8369
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8370
8371
        $lastPosition = null;
8372
        foreach ($arrHide as $key => $value) {
8373
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8374
            $lastPosition = $key;
8375
        }
8376
8377
        if (!empty($s_selected_position)) {
8378
            $position->setSelected($s_selected_position);
8379
        }
8380
8381
        // When new chapter add at the end
8382
        if ($action === 'add_item') {
8383
            $position->setSelected($lastPosition);
8384
        }
8385
8386
        if (is_array($arrLP)) {
8387
            reset($arrLP);
8388
        }
8389
8390
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8391
8392
        //fix in order to use the tab
8393
        if ($item_type === 'dir') {
8394
            $form->addElement('hidden', 'type', 'dir');
8395
        }
8396
8397
        $extension = null;
8398
        if (!empty($item_path)) {
8399
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8400
        }
8401
8402
        //assets can't be modified
8403
        //$item_type == 'asset' ||
8404
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8405
            if ($item_type == 'sco') {
8406
                $form->addElement(
8407
                    'html',
8408
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8409
                );
8410
            }
8411
            $renderer = $form->defaultRenderer();
8412
            $renderer->setElementTemplate(
8413
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8414
                'content_lp'
8415
            );
8416
8417
            $relative_prefix = '';
8418
            $editor_config = [
8419
                'ToolbarSet' => 'LearningPathDocuments',
8420
                'Width' => '100%',
8421
                'Height' => '500',
8422
                'FullPage' => true,
8423
                'CreateDocumentDir' => $relative_prefix,
8424
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8425
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8426
            ];
8427
8428
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8429
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8430
            $defaults['content_lp'] = file_get_contents($content_path);
8431
        }
8432
8433
        if (!empty($id)) {
8434
            $form->addHidden('id', $id);
8435
        }
8436
8437
        $form->addElement('hidden', 'type', $item_type);
8438
        $form->addElement('hidden', 'post_time', time());
8439
        $form->setDefaults($defaults);
8440
8441
        return $form->returnForm();
8442
    }
8443
8444
    /**
8445
     * @return string
8446
     */
8447
    public function getCurrentBuildingModeURL()
8448
    {
8449
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8450
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8451
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8452
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8453
8454
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8455
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8456
8457
        return $currentUrl;
8458
    }
8459
8460
    /**
8461
     * Returns the form to update or create a document.
8462
     *
8463
     * @param string $action     (add/edit)
8464
     * @param int    $id         ID of the lp_item (if already exists)
8465
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8466
     *
8467
     * @throws Exception
8468
     * @throws HTML_QuickForm_Error
8469
     *
8470
     * @return string HTML form
8471
     */
8472
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8473
    {
8474
        $course_id = api_get_course_int_id();
8475
        $_course = api_get_course_info();
8476
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8477
8478
        $no_display_edit_textarea = false;
8479
        $item_description = '';
8480
        //If action==edit document
8481
        //We don't display the document form if it's not an editable document (html or txt file)
8482
        if ($action === 'edit') {
8483
            if (is_array($extra_info)) {
8484
                $path_parts = pathinfo($extra_info['dir']);
8485
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8486
                    $no_display_edit_textarea = true;
8487
                }
8488
            }
8489
        }
8490
        $no_display_add = false;
8491
8492
        // If action==add an existing document
8493
        // We don't display the document form if it's not an editable document (html or txt file).
8494
        if ($action === 'add') {
8495
            if (is_numeric($extra_info)) {
8496
                $extra_info = (int) $extra_info;
8497
                $sql_doc = "SELECT path FROM $tbl_doc
8498
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8499
                $result = Database::query($sql_doc);
8500
                $path_file = Database::result($result, 0, 0);
8501
                $path_parts = pathinfo($path_file);
8502
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8503
                    $no_display_add = true;
8504
                }
8505
            }
8506
        }
8507
8508
        $item_title = '';
8509
        $item_description = '';
8510
        if ($id != 0 && is_array($extra_info)) {
8511
            $item_title = stripslashes($extra_info['title']);
8512
            $item_description = stripslashes($extra_info['description']);
8513
            if (empty($item_title)) {
8514
                $path_parts = pathinfo($extra_info['path']);
8515
                $item_title = stripslashes($path_parts['filename']);
8516
            }
8517
        } elseif (is_numeric($extra_info)) {
8518
            $sql = "SELECT path, title FROM $tbl_doc
8519
                    WHERE
8520
                        c_id = ".$course_id." AND
8521
                        iid = ".intval($extra_info);
8522
            $result = Database::query($sql);
8523
            $row = Database::fetch_array($result);
8524
            $item_title = $row['title'];
8525
            $item_title = str_replace('_', ' ', $item_title);
8526
            if (empty($item_title)) {
8527
                $path_parts = pathinfo($row['path']);
8528
                $item_title = stripslashes($path_parts['filename']);
8529
            }
8530
        }
8531
8532
        $return = '<legend>';
8533
        $parent = 0;
8534
        if ($id != 0 && is_array($extra_info)) {
8535
            $parent = $extra_info['parent_item_id'];
8536
        }
8537
8538
        $arrLP = $this->getItemsForForm();
8539
        $this->tree_array($arrLP);
8540
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8541
        unset($this->arrMenu);
8542
8543
        if ($action === 'add') {
8544
            $return .= get_lang('CreateTheDocument');
8545
        } elseif ($action === 'move') {
8546
            $return .= get_lang('MoveTheCurrentDocument');
8547
        } else {
8548
            $return .= get_lang('EditTheCurrentDocument');
8549
        }
8550
        $return .= '</legend>';
8551
8552
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8553
            $return .= Display::return_message(
8554
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8555
                false
8556
            );
8557
        }
8558
        $form = new FormValidator(
8559
            'form',
8560
            'POST',
8561
            $this->getCurrentBuildingModeURL(),
8562
            '',
8563
            ['enctype' => 'multipart/form-data']
8564
        );
8565
        $defaults['title'] = Security::remove_XSS($item_title);
8566
        if (empty($item_title)) {
8567
            $defaults['title'] = Security::remove_XSS($item_title);
8568
        }
8569
        $defaults['description'] = $item_description;
8570
        $form->addElement('html', $return);
8571
8572
        if ($action !== 'move') {
8573
            $data = $this->generate_lp_folder($_course);
8574
            if ($action !== 'edit') {
8575
                $folders = DocumentManager::get_all_document_folders(
8576
                    $_course,
8577
                    0,
8578
                    true
8579
                );
8580
                DocumentManager::build_directory_selector(
8581
                    $folders,
8582
                    '',
8583
                    [],
8584
                    true,
8585
                    $form,
8586
                    'directory_parent_id'
8587
                );
8588
            }
8589
8590
            if (isset($data['id'])) {
8591
                $defaults['directory_parent_id'] = $data['id'];
8592
            }
8593
            $this->setItemTitle($form);
8594
        }
8595
8596
        $arrHide[0]['value'] = $this->name;
8597
        $arrHide[0]['padding'] = 20;
8598
8599
        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...
8600
            if ($action !== 'add') {
8601
                if ($arrLP[$i]['item_type'] === 'dir' &&
8602
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8603
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8604
                ) {
8605
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8606
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8607
                }
8608
            } else {
8609
                if ($arrLP[$i]['item_type'] == 'dir') {
8610
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8611
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8612
                }
8613
            }
8614
        }
8615
8616
        $parentSelect = $form->addSelect(
8617
            'parent',
8618
            get_lang('Parent'),
8619
            [],
8620
            [
8621
                'id' => 'idParent',
8622
                'onchange' => 'javascript: load_cbo(this.value);',
8623
            ]
8624
        );
8625
8626
        $my_count = 0;
8627
        foreach ($arrHide as $key => $value) {
8628
            if ($my_count != 0) {
8629
                // The LP name is also the first section and is not in the same charset like the other sections.
8630
                $value['value'] = Security::remove_XSS($value['value']);
8631
                $parentSelect->addOption(
8632
                    $value['value'],
8633
                    $key,
8634
                    'style="padding-left:'.$value['padding'].'px;"'
8635
                );
8636
            } else {
8637
                $value['value'] = Security::remove_XSS($value['value']);
8638
                $parentSelect->addOption(
8639
                    $value['value'],
8640
                    $key,
8641
                    'style="padding-left:'.$value['padding'].'px;"'
8642
                );
8643
            }
8644
            $my_count++;
8645
        }
8646
8647
        if (!empty($id)) {
8648
            $parentSelect->setSelected($parent);
8649
        } else {
8650
            $parent_item_id = Session::read('parent_item_id', 0);
8651
            $parentSelect->setSelected($parent_item_id);
8652
        }
8653
8654
        if (is_array($arrLP)) {
8655
            reset($arrLP);
8656
        }
8657
8658
        $arrHide = [];
8659
        // POSITION
8660
        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...
8661
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8662
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8663
            ) {
8664
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8665
            }
8666
        }
8667
8668
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8669
8670
        $position = $form->addSelect(
8671
            'previous',
8672
            get_lang('Position'),
8673
            [],
8674
            ['id' => 'previous']
8675
        );
8676
8677
        $position->addOption(get_lang('FirstPosition'), 0);
8678
        foreach ($arrHide as $key => $value) {
8679
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8680
            $position->addOption(
8681
                $value['value'],
8682
                $key,
8683
                'style="padding-left:'.$padding.'px;"'
8684
            );
8685
        }
8686
8687
        $position->setSelected($selectedPosition);
8688
8689
        if (is_array($arrLP)) {
8690
            reset($arrLP);
8691
        }
8692
8693
        if ($action !== 'move') {
8694
            $arrHide = [];
8695
            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...
8696
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8697
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8698
                ) {
8699
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8700
                }
8701
            }
8702
8703
            if (!$no_display_add) {
8704
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8705
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8706
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8707
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8708
                ) {
8709
                    if (isset($_POST['content'])) {
8710
                        $content = stripslashes($_POST['content']);
8711
                    } elseif (is_array($extra_info)) {
8712
                        //If it's an html document or a text file
8713
                        if (!$no_display_edit_textarea) {
8714
                            $content = $this->display_document(
8715
                                $extra_info['path'],
8716
                                false,
8717
                                false
8718
                            );
8719
                        }
8720
                    } elseif (is_numeric($extra_info)) {
8721
                        $content = $this->display_document(
8722
                            $extra_info,
8723
                            false,
8724
                            false
8725
                        );
8726
                    } else {
8727
                        $content = '';
8728
                    }
8729
8730
                    if (!$no_display_edit_textarea) {
8731
                        // We need to calculate here some specific settings for the online editor.
8732
                        // The calculated settings work for documents in the Documents tool
8733
                        // (on the root or in subfolders).
8734
                        // For documents in native scorm packages it is unclear whether the
8735
                        // online editor should be activated or not.
8736
8737
                        // A new document, it is in the root of the repository.
8738
                        $relative_path = '';
8739
                        $relative_prefix = '';
8740
                        if (is_array($extra_info) && $extra_info != 'new') {
8741
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8742
                            $relative_path = explode('/', $extra_info['dir']);
8743
                            $cnt = count($relative_path) - 2;
8744
                            if ($cnt < 0) {
8745
                                $cnt = 0;
8746
                            }
8747
                            $relative_prefix = str_repeat('../', $cnt);
8748
                            $relative_path = array_slice($relative_path, 1, $cnt);
8749
                            $relative_path = implode('/', $relative_path);
8750
                            if (strlen($relative_path) > 0) {
8751
                                $relative_path = $relative_path.'/';
8752
                            }
8753
                        } else {
8754
                            $result = $this->generate_lp_folder($_course);
8755
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8756
                            $relative_prefix = '../../';
8757
                        }
8758
8759
                        $editor_config = [
8760
                            'ToolbarSet' => 'LearningPathDocuments',
8761
                            'Width' => '100%',
8762
                            'Height' => '500',
8763
                            'FullPage' => true,
8764
                            'CreateDocumentDir' => $relative_prefix,
8765
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8766
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8767
                        ];
8768
8769
                        if ($_GET['action'] === 'add_item') {
8770
                            $class = 'add';
8771
                            $text = get_lang('LPCreateDocument');
8772
                        } else {
8773
                            if ($_GET['action'] === 'edit_item') {
8774
                                $class = 'save';
8775
                                $text = get_lang('SaveDocument');
8776
                            }
8777
                        }
8778
8779
                        $form->addButtonSave($text, 'submit_button');
8780
                        $renderer = $form->defaultRenderer();
8781
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8782
                        $form->addElement('html', '<div class="editor-lp">');
8783
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8784
                        $form->addElement('html', '</div>');
8785
                        $defaults['content_lp'] = $content;
8786
                    }
8787
                } elseif (is_numeric($extra_info)) {
8788
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8789
8790
                    $return = $this->display_document($extra_info, true, true, true);
8791
                    $form->addElement('html', $return);
8792
                }
8793
            }
8794
        }
8795
        if (isset($extra_info['item_type']) &&
8796
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8797
        ) {
8798
            $parentSelect->freeze();
8799
            $position->freeze();
8800
        }
8801
8802
        if ($action === 'move') {
8803
            $form->addElement('hidden', 'title', $item_title);
8804
            $form->addElement('hidden', 'description', $item_description);
8805
        }
8806
        if (is_numeric($extra_info)) {
8807
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8808
            $form->addElement('hidden', 'path', $extra_info);
8809
        } elseif (is_array($extra_info)) {
8810
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8811
            $form->addElement('hidden', 'path', $extra_info['path']);
8812
        }
8813
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8814
        $form->addElement('hidden', 'post_time', time());
8815
        $form->setDefaults($defaults);
8816
8817
        return $form->returnForm();
8818
    }
8819
8820
    /**
8821
     * Returns the form to update or create a read-out text.
8822
     *
8823
     * @param string $action     "add" or "edit"
8824
     * @param int    $id         ID of the lp_item (if already exists)
8825
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8826
     *
8827
     * @throws Exception
8828
     * @throws HTML_QuickForm_Error
8829
     *
8830
     * @return string HTML form
8831
     */
8832
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8833
    {
8834
        $course_id = api_get_course_int_id();
8835
        $_course = api_get_course_info();
8836
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8837
8838
        $no_display_edit_textarea = false;
8839
        //If action==edit document
8840
        //We don't display the document form if it's not an editable document (html or txt file)
8841
        if ($action == 'edit') {
8842
            if (is_array($extra_info)) {
8843
                $path_parts = pathinfo($extra_info['dir']);
8844
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8845
                    $no_display_edit_textarea = true;
8846
                }
8847
            }
8848
        }
8849
        $no_display_add = false;
8850
8851
        $item_title = '';
8852
        $item_description = '';
8853
        if ($id != 0 && is_array($extra_info)) {
8854
            $item_title = stripslashes($extra_info['title']);
8855
            $item_description = stripslashes($extra_info['description']);
8856
            $item_terms = stripslashes($extra_info['terms']);
8857
            if (empty($item_title)) {
8858
                $path_parts = pathinfo($extra_info['path']);
8859
                $item_title = stripslashes($path_parts['filename']);
8860
            }
8861
        } elseif (is_numeric($extra_info)) {
8862
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8863
            $result = Database::query($sql);
8864
            $row = Database::fetch_array($result);
8865
            $item_title = $row['title'];
8866
            $item_title = str_replace('_', ' ', $item_title);
8867
            if (empty($item_title)) {
8868
                $path_parts = pathinfo($row['path']);
8869
                $item_title = stripslashes($path_parts['filename']);
8870
            }
8871
        }
8872
8873
        $parent = 0;
8874
        if ($id != 0 && is_array($extra_info)) {
8875
            $parent = $extra_info['parent_item_id'];
8876
        }
8877
8878
        $arrLP = $this->getItemsForForm();
8879
        $this->tree_array($arrLP);
8880
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8881
        unset($this->arrMenu);
8882
8883
        if ($action === 'add') {
8884
            $formHeader = get_lang('CreateTheDocument');
8885
        } else {
8886
            $formHeader = get_lang('EditTheCurrentDocument');
8887
        }
8888
8889
        if ('edit' === $action) {
8890
            $urlAudioIcon = Display::url(
8891
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
8892
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8893
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8894
            );
8895
        } else {
8896
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
8897
        }
8898
8899
        $form = new FormValidator(
8900
            'frm_add_reading',
8901
            'POST',
8902
            $this->getCurrentBuildingModeURL(),
8903
            '',
8904
            ['enctype' => 'multipart/form-data']
8905
        );
8906
        $form->addHeader($formHeader);
8907
        $form->addHtml(
8908
            Display::return_message(
8909
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
8910
                'normal',
8911
                false
8912
            )
8913
        );
8914
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8915
        $defaults['description'] = $item_description;
8916
8917
        $data = $this->generate_lp_folder($_course);
8918
8919
        if ($action != 'edit') {
8920
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8921
            DocumentManager::build_directory_selector(
8922
                $folders,
8923
                '',
8924
                [],
8925
                true,
8926
                $form,
8927
                'directory_parent_id'
8928
            );
8929
        }
8930
8931
        if (isset($data['id'])) {
8932
            $defaults['directory_parent_id'] = $data['id'];
8933
        }
8934
        $this->setItemTitle($form);
8935
8936
        $arrHide[0]['value'] = $this->name;
8937
        $arrHide[0]['padding'] = 20;
8938
8939
        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...
8940
            if ($action != 'add') {
8941
                if ($arrLP[$i]['item_type'] == 'dir' &&
8942
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8943
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8944
                ) {
8945
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8946
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8947
                }
8948
            } else {
8949
                if ($arrLP[$i]['item_type'] == 'dir') {
8950
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8951
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8952
                }
8953
            }
8954
        }
8955
8956
        $parent_select = $form->addSelect(
8957
            'parent',
8958
            get_lang('Parent'),
8959
            [],
8960
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8961
        );
8962
8963
        $my_count = 0;
8964
        foreach ($arrHide as $key => $value) {
8965
            if ($my_count != 0) {
8966
                // The LP name is also the first section and is not in the same charset like the other sections.
8967
                $value['value'] = Security::remove_XSS($value['value']);
8968
                $parent_select->addOption(
8969
                    $value['value'],
8970
                    $key,
8971
                    'style="padding-left:'.$value['padding'].'px;"'
8972
                );
8973
            } else {
8974
                $value['value'] = Security::remove_XSS($value['value']);
8975
                $parent_select->addOption(
8976
                    $value['value'],
8977
                    $key,
8978
                    'style="padding-left:'.$value['padding'].'px;"'
8979
                );
8980
            }
8981
            $my_count++;
8982
        }
8983
8984
        if (!empty($id)) {
8985
            $parent_select->setSelected($parent);
8986
        } else {
8987
            $parent_item_id = Session::read('parent_item_id', 0);
8988
            $parent_select->setSelected($parent_item_id);
8989
        }
8990
8991
        if (is_array($arrLP)) {
8992
            reset($arrLP);
8993
        }
8994
8995
        $arrHide = [];
8996
        $s_selected_position = null;
8997
8998
        // POSITION
8999
        $lastPosition = null;
9000
9001
        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...
9002
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9003
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9004
            ) {
9005
                if ((isset($extra_info['previous_item_id']) &&
9006
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9007
                ) {
9008
                    $s_selected_position = $arrLP[$i]['id'];
9009
                }
9010
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9011
            }
9012
            $lastPosition = $arrLP[$i]['id'];
9013
        }
9014
9015
        if (empty($s_selected_position)) {
9016
            $s_selected_position = $lastPosition;
9017
        }
9018
9019
        $position = $form->addSelect(
9020
            'previous',
9021
            get_lang('Position'),
9022
            []
9023
        );
9024
        $position->addOption(get_lang('FirstPosition'), 0);
9025
9026
        foreach ($arrHide as $key => $value) {
9027
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9028
            $position->addOption(
9029
                $value['value'],
9030
                $key,
9031
                'style="padding-left:'.$padding.'px;"'
9032
            );
9033
        }
9034
        $position->setSelected($s_selected_position);
9035
9036
        if (is_array($arrLP)) {
9037
            reset($arrLP);
9038
        }
9039
9040
        $arrHide = [];
9041
9042
        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...
9043
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9044
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9045
            ) {
9046
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9047
            }
9048
        }
9049
9050
        if (!$no_display_add) {
9051
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9052
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9053
9054
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9055
                if (!$no_display_edit_textarea) {
9056
                    $content = '';
9057
9058
                    if (isset($_POST['content'])) {
9059
                        $content = stripslashes($_POST['content']);
9060
                    } elseif (is_array($extra_info)) {
9061
                        $content = $this->display_document($extra_info['path'], false, false);
9062
                    } elseif (is_numeric($extra_info)) {
9063
                        $content = $this->display_document($extra_info, false, false);
9064
                    }
9065
9066
                    // A new document, it is in the root of the repository.
9067
                    if (is_array($extra_info) && $extra_info != 'new') {
9068
                    } else {
9069
                        $this->generate_lp_folder($_course);
9070
                    }
9071
9072
                    if ($_GET['action'] == 'add_item') {
9073
                        $text = get_lang('LPCreateDocument');
9074
                    } else {
9075
                        $text = get_lang('SaveDocument');
9076
                    }
9077
9078
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9079
                    $form
9080
                        ->defaultRenderer()
9081
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9082
                    $form->addButtonSave($text, 'submit_button');
9083
                    $defaults['content_lp'] = $content;
9084
                }
9085
            } elseif (is_numeric($extra_info)) {
9086
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9087
9088
                $return = $this->display_document($extra_info, true, true, true);
9089
                $form->addElement('html', $return);
9090
            }
9091
        }
9092
9093
        if (is_numeric($extra_info)) {
9094
            $form->addElement('hidden', 'path', $extra_info);
9095
        } elseif (is_array($extra_info)) {
9096
            $form->addElement('hidden', 'path', $extra_info['path']);
9097
        }
9098
9099
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9100
        $form->addElement('hidden', 'post_time', time());
9101
        $form->setDefaults($defaults);
9102
9103
        return $form->returnForm();
9104
    }
9105
9106
    /**
9107
     * @param array  $courseInfo
9108
     * @param string $content
9109
     * @param string $title
9110
     * @param int    $parentId
9111
     *
9112
     * @throws \Doctrine\ORM\ORMException
9113
     * @throws \Doctrine\ORM\OptimisticLockException
9114
     * @throws \Doctrine\ORM\TransactionRequiredException
9115
     *
9116
     * @return int
9117
     */
9118
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9119
    {
9120
        $creatorId = api_get_user_id();
9121
        $sessionId = api_get_session_id();
9122
9123
        // Generates folder
9124
        $result = $this->generate_lp_folder($courseInfo);
9125
        $dir = $result['dir'];
9126
9127
        if (empty($parentId) || $parentId == '/') {
9128
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9129
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9130
9131
            if ($parentId === '/') {
9132
                $dir = '/';
9133
            }
9134
9135
            // Please, do not modify this dirname formatting.
9136
            if (strstr($dir, '..')) {
9137
                $dir = '/';
9138
            }
9139
9140
            if (!empty($dir[0]) && $dir[0] == '.') {
9141
                $dir = substr($dir, 1);
9142
            }
9143
            if (!empty($dir[0]) && $dir[0] != '/') {
9144
                $dir = '/'.$dir;
9145
            }
9146
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9147
                $dir .= '/';
9148
            }
9149
        } else {
9150
            $parentInfo = DocumentManager::get_document_data_by_id(
9151
                $parentId,
9152
                $courseInfo['code']
9153
            );
9154
            if (!empty($parentInfo)) {
9155
                $dir = $parentInfo['path'].'/';
9156
            }
9157
        }
9158
9159
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9160
9161
        if (!is_dir($filepath)) {
9162
            $dir = '/';
9163
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9164
        }
9165
9166
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9167
9168
        if (!empty($title)) {
9169
            $title = api_replace_dangerous_char(stripslashes($title));
9170
        } else {
9171
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9172
        }
9173
9174
        $title = disable_dangerous_file($title);
9175
        $filename = $title;
9176
        $content = !empty($content) ? $content : $_POST['content_lp'];
9177
        $tmpFileName = $filename;
9178
9179
        $i = 0;
9180
        while (file_exists($filepath.$tmpFileName.'.html')) {
9181
            $tmpFileName = $filename.'_'.++$i;
9182
        }
9183
9184
        $filename = $tmpFileName.'.html';
9185
        $content = stripslashes($content);
9186
9187
        if (file_exists($filepath.$filename)) {
9188
            return 0;
9189
        }
9190
9191
        $putContent = file_put_contents($filepath.$filename, $content);
9192
9193
        if ($putContent === false) {
9194
            return 0;
9195
        }
9196
9197
        $fileSize = filesize($filepath.$filename);
9198
        $saveFilePath = $dir.$filename;
9199
9200
        $documentId = add_document(
9201
            $courseInfo,
9202
            $saveFilePath,
9203
            'file',
9204
            $fileSize,
9205
            $tmpFileName,
9206
            '',
9207
            0, //readonly
9208
            true,
9209
            null,
9210
            $sessionId,
9211
            $creatorId
9212
        );
9213
9214
        if (!$documentId) {
9215
            return 0;
9216
        }
9217
9218
        api_item_property_update(
9219
            $courseInfo,
9220
            TOOL_DOCUMENT,
9221
            $documentId,
9222
            'DocumentAdded',
9223
            $creatorId,
9224
            null,
9225
            null,
9226
            null,
9227
            null,
9228
            $sessionId
9229
        );
9230
9231
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9232
        $newTitle = $originalTitle;
9233
9234
        if ($newComment || $newTitle) {
9235
            $em = Database::getManager();
9236
9237
            /** @var CDocument $doc */
9238
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9239
9240
            if ($newComment) {
9241
                $doc->setComment($newComment);
9242
            }
9243
9244
            if ($newTitle) {
9245
                $doc->setTitle($newTitle);
9246
            }
9247
9248
            $em->persist($doc);
9249
            $em->flush();
9250
        }
9251
9252
        return $documentId;
9253
    }
9254
9255
    /**
9256
     * Return HTML form to add/edit a link item.
9257
     *
9258
     * @param string $action     (add/edit)
9259
     * @param int    $id         Item ID if exists
9260
     * @param mixed  $extra_info
9261
     *
9262
     * @throws Exception
9263
     * @throws HTML_QuickForm_Error
9264
     *
9265
     * @return string HTML form
9266
     */
9267
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9268
    {
9269
        $course_id = api_get_course_int_id();
9270
        $tbl_link = Database::get_course_table(TABLE_LINK);
9271
9272
        $item_title = '';
9273
        $item_description = '';
9274
        $item_url = '';
9275
9276
        if ($id != 0 && is_array($extra_info)) {
9277
            $item_title = stripslashes($extra_info['title']);
9278
            $item_description = stripslashes($extra_info['description']);
9279
            $item_url = stripslashes($extra_info['url']);
9280
        } elseif (is_numeric($extra_info)) {
9281
            $extra_info = (int) $extra_info;
9282
            $sql = "SELECT title, description, url
9283
                    FROM $tbl_link
9284
                    WHERE c_id = $course_id AND iid = $extra_info";
9285
            $result = Database::query($sql);
9286
            $row = Database::fetch_array($result);
9287
            $item_title = $row['title'];
9288
            $item_description = $row['description'];
9289
            $item_url = $row['url'];
9290
        }
9291
9292
        $form = new FormValidator(
9293
            'edit_link',
9294
            'POST',
9295
            $this->getCurrentBuildingModeURL()
9296
        );
9297
        $defaults = [];
9298
        $parent = 0;
9299
        if ($id != 0 && is_array($extra_info)) {
9300
            $parent = $extra_info['parent_item_id'];
9301
        }
9302
9303
        $arrLP = $this->getItemsForForm();
9304
9305
        $this->tree_array($arrLP);
9306
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9307
        unset($this->arrMenu);
9308
9309
        if ($action == 'add') {
9310
            $legend = get_lang('CreateTheLink');
9311
        } elseif ($action == 'move') {
9312
            $legend = get_lang('MoveCurrentLink');
9313
        } else {
9314
            $legend = get_lang('EditCurrentLink');
9315
        }
9316
9317
        $form->addHeader($legend);
9318
9319
        if ($action != 'move') {
9320
            $this->setItemTitle($form);
9321
            $defaults['title'] = $item_title;
9322
        }
9323
9324
        $selectParent = $form->addSelect(
9325
            'parent',
9326
            get_lang('Parent'),
9327
            [],
9328
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9329
        );
9330
        $selectParent->addOption($this->name, 0);
9331
        $arrHide = [
9332
            $id,
9333
        ];
9334
9335
        $parent_item_id = Session::read('parent_item_id', 0);
9336
9337
        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...
9338
            if ($action != 'add') {
9339
                if (
9340
                    ($arrLP[$i]['item_type'] == 'dir') &&
9341
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9342
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9343
                ) {
9344
                    $selectParent->addOption(
9345
                        $arrLP[$i]['title'],
9346
                        $arrLP[$i]['id'],
9347
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9348
                    );
9349
9350
                    if ($parent == $arrLP[$i]['id']) {
9351
                        $selectParent->setSelected($arrLP[$i]['id']);
9352
                    }
9353
                } else {
9354
                    $arrHide[] = $arrLP[$i]['id'];
9355
                }
9356
            } else {
9357
                if ($arrLP[$i]['item_type'] == 'dir') {
9358
                    $selectParent->addOption(
9359
                        $arrLP[$i]['title'],
9360
                        $arrLP[$i]['id'],
9361
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9362
                    );
9363
9364
                    if ($parent_item_id == $arrLP[$i]['id']) {
9365
                        $selectParent->setSelected($arrLP[$i]['id']);
9366
                    }
9367
                }
9368
            }
9369
        }
9370
9371
        if (is_array($arrLP)) {
9372
            reset($arrLP);
9373
        }
9374
9375
        $selectPrevious = $form->addSelect(
9376
            'previous',
9377
            get_lang('Position'),
9378
            [],
9379
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9380
        );
9381
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9382
9383
        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...
9384
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9385
                $selectPrevious->addOption(
9386
                    $arrLP[$i]['title'],
9387
                    $arrLP[$i]['id']
9388
                );
9389
9390
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9391
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9392
                } elseif ($action == 'add') {
9393
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9394
                }
9395
            }
9396
        }
9397
9398
        if ($action != 'move') {
9399
            $urlAttributes = ['class' => 'learnpath_item_form'];
9400
9401
            if (is_numeric($extra_info)) {
9402
                $urlAttributes['disabled'] = 'disabled';
9403
            }
9404
9405
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9406
            $defaults['url'] = $item_url;
9407
            $arrHide = [];
9408
            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...
9409
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9410
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9411
                }
9412
            }
9413
        }
9414
9415
        if ($action == 'add') {
9416
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9417
        } else {
9418
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9419
        }
9420
9421
        if ($action == 'move') {
9422
            $form->addHidden('title', $item_title);
9423
            $form->addHidden('description', $item_description);
9424
        }
9425
9426
        if (is_numeric($extra_info)) {
9427
            $form->addHidden('path', $extra_info);
9428
        } elseif (is_array($extra_info)) {
9429
            $form->addHidden('path', $extra_info['path']);
9430
        }
9431
        $form->addHidden('type', TOOL_LINK);
9432
        $form->addHidden('post_time', time());
9433
        $form->setDefaults($defaults);
9434
9435
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9436
    }
9437
9438
    /**
9439
     * Return HTML form to add/edit a student publication (work).
9440
     *
9441
     * @param string $action
9442
     * @param int    $id         Item ID if already exists
9443
     * @param string $extra_info
9444
     *
9445
     * @throws Exception
9446
     *
9447
     * @return string HTML form
9448
     */
9449
    public function display_student_publication_form(
9450
        $action = 'add',
9451
        $id = 0,
9452
        $extra_info = ''
9453
    ) {
9454
        $course_id = api_get_course_int_id();
9455
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9456
9457
        $item_title = get_lang('Student_publication');
9458
        if ($id != 0 && is_array($extra_info)) {
9459
            $item_title = stripslashes($extra_info['title']);
9460
            $item_description = stripslashes($extra_info['description']);
9461
        } elseif (is_numeric($extra_info)) {
9462
            $extra_info = (int) $extra_info;
9463
            $sql = "SELECT title, description
9464
                    FROM $tbl_publication
9465
                    WHERE c_id = $course_id AND id = ".$extra_info;
9466
9467
            $result = Database::query($sql);
9468
            $row = Database::fetch_array($result);
9469
            if ($row) {
9470
                $item_title = $row['title'];
9471
            }
9472
        }
9473
9474
        $parent = 0;
9475
        if ($id != 0 && is_array($extra_info)) {
9476
            $parent = $extra_info['parent_item_id'];
9477
        }
9478
9479
        $arrLP = $this->getItemsForForm();
9480
9481
        $this->tree_array($arrLP);
9482
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9483
        unset($this->arrMenu);
9484
9485
        $form = new FormValidator('frm_student_publication', 'post', '#');
9486
9487
        if ($action == 'add') {
9488
            $form->addHeader(get_lang('Student_publication'));
9489
        } elseif ($action == 'move') {
9490
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9491
        } else {
9492
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9493
        }
9494
9495
        if ($action != 'move') {
9496
            $this->setItemTitle($form);
9497
        }
9498
9499
        $parentSelect = $form->addSelect(
9500
            'parent',
9501
            get_lang('Parent'),
9502
            ['0' => $this->name],
9503
            [
9504
                'onchange' => 'javascript: load_cbo(this.value);',
9505
                'class' => 'learnpath_item_form',
9506
                'id' => 'idParent',
9507
            ]
9508
        );
9509
9510
        $arrHide = [$id];
9511
        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...
9512
            if ($action != 'add') {
9513
                if (
9514
                    ($arrLP[$i]['item_type'] == 'dir') &&
9515
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9516
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9517
                ) {
9518
                    $parentSelect->addOption(
9519
                        $arrLP[$i]['title'],
9520
                        $arrLP[$i]['id'],
9521
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9522
                    );
9523
9524
                    if ($parent == $arrLP[$i]['id']) {
9525
                        $parentSelect->setSelected($arrLP[$i]['id']);
9526
                    }
9527
                } else {
9528
                    $arrHide[] = $arrLP[$i]['id'];
9529
                }
9530
            } else {
9531
                if ($arrLP[$i]['item_type'] == 'dir') {
9532
                    $parentSelect->addOption(
9533
                        $arrLP[$i]['title'],
9534
                        $arrLP[$i]['id'],
9535
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9536
                    );
9537
9538
                    if ($parent == $arrLP[$i]['id']) {
9539
                        $parentSelect->setSelected($arrLP[$i]['id']);
9540
                    }
9541
                }
9542
            }
9543
        }
9544
9545
        if (is_array($arrLP)) {
9546
            reset($arrLP);
9547
        }
9548
9549
        $previousSelect = $form->addSelect(
9550
            'previous',
9551
            get_lang('Position'),
9552
            ['0' => get_lang('FirstPosition')],
9553
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9554
        );
9555
9556
        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...
9557
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9558
                $previousSelect->addOption(
9559
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9560
                    $arrLP[$i]['id']
9561
                );
9562
9563
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9564
                    $previousSelect->setSelected($arrLP[$i]['id']);
9565
                } elseif ($action == 'add') {
9566
                    $previousSelect->setSelected($arrLP[$i]['id']);
9567
                }
9568
            }
9569
        }
9570
9571
        if ($action == 'add') {
9572
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9573
        } else {
9574
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9575
        }
9576
9577
        if ($action == 'move') {
9578
            $form->addHidden('title', $item_title);
9579
            $form->addHidden('description', $item_description);
9580
        }
9581
9582
        if (is_numeric($extra_info)) {
9583
            $form->addHidden('path', $extra_info);
9584
        } elseif (is_array($extra_info)) {
9585
            $form->addHidden('path', $extra_info['path']);
9586
        }
9587
9588
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9589
        $form->addHidden('post_time', time());
9590
        $form->setDefaults(['title' => $item_title]);
9591
9592
        $return = '<div class="sectioncomment">';
9593
        $return .= $form->returnForm();
9594
        $return .= '</div>';
9595
9596
        return $return;
9597
    }
9598
9599
    /**
9600
     * Displays the menu for manipulating a step.
9601
     *
9602
     * @param id     $item_id
9603
     * @param string $item_type
9604
     *
9605
     * @return string
9606
     */
9607
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9608
    {
9609
        $_course = api_get_course_info();
9610
        $course_code = api_get_course_id();
9611
        $item_id = (int) $item_id;
9612
9613
        $return = '<div class="actions">';
9614
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9615
        $sql = "SELECT * FROM $tbl_lp_item
9616
                WHERE iid = ".$item_id;
9617
        $result = Database::query($sql);
9618
        $row = Database::fetch_assoc($result);
9619
9620
        $audio_player = null;
9621
        // We display an audio player if needed.
9622
        if (!empty($row['audio'])) {
9623
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9624
9625
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9626
                .'<audio src="'.$webAudioPath.'" controls>'
9627
                .'</div><br>';
9628
        }
9629
9630
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9631
9632
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9633
            $return .= Display::url(
9634
                Display::return_icon(
9635
                    'edit.png',
9636
                    get_lang('Edit'),
9637
                    [],
9638
                    ICON_SIZE_SMALL
9639
                ),
9640
                $url.'&action=edit_item&path_item='.$row['path']
9641
            );
9642
9643
            $return .= Display::url(
9644
                Display::return_icon(
9645
                    'move.png',
9646
                    get_lang('Move'),
9647
                    [],
9648
                    ICON_SIZE_SMALL
9649
                ),
9650
                $url.'&action=move_item'
9651
            );
9652
        }
9653
9654
        // Commented for now as prerequisites cannot be added to chapters.
9655
        if ($item_type != 'dir') {
9656
            $return .= Display::url(
9657
                Display::return_icon(
9658
                    'accept.png',
9659
                    get_lang('LearnpathPrerequisites'),
9660
                    [],
9661
                    ICON_SIZE_SMALL
9662
                ),
9663
                $url.'&action=edit_item_prereq'
9664
            );
9665
        }
9666
        $return .= Display::url(
9667
            Display::return_icon(
9668
                'delete.png',
9669
                get_lang('Delete'),
9670
                [],
9671
                ICON_SIZE_SMALL
9672
            ),
9673
            $url.'&action=delete_item'
9674
        );
9675
9676
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9677
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9678
            if (empty($documentData)) {
9679
                // Try with iid
9680
                $table = Database::get_course_table(TABLE_DOCUMENT);
9681
                $sql = "SELECT path FROM $table
9682
                        WHERE
9683
                              c_id = ".api_get_course_int_id()." AND
9684
                              iid = ".$row['path']." AND
9685
                              path NOT LIKE '%_DELETED_%'";
9686
                $result = Database::query($sql);
9687
                $documentData = Database::fetch_array($result);
9688
                if ($documentData) {
9689
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9690
                }
9691
            }
9692
            if (isset($documentData['absolute_path_from_document'])) {
9693
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9694
            }
9695
        }
9696
9697
        $return .= '</div>';
9698
9699
        if (!empty($audio_player)) {
9700
            $return .= $audio_player;
9701
        }
9702
9703
        return $return;
9704
    }
9705
9706
    /**
9707
     * Creates the javascript needed for filling up the checkboxes without page reload.
9708
     *
9709
     * @return string
9710
     */
9711
    public function get_js_dropdown_array()
9712
    {
9713
        $course_id = api_get_course_int_id();
9714
        $return = 'var child_name = new Array();'."\n";
9715
        $return .= 'var child_value = new Array();'."\n\n";
9716
        $return .= 'child_name[0] = new Array();'."\n";
9717
        $return .= 'child_value[0] = new Array();'."\n\n";
9718
9719
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9720
        $sql = "SELECT * FROM ".$tbl_lp_item."
9721
                WHERE
9722
                    c_id = $course_id AND
9723
                    lp_id = ".$this->lp_id." AND
9724
                    parent_item_id = 0
9725
                ORDER BY display_order ASC";
9726
        $res_zero = Database::query($sql);
9727
        $i = 0;
9728
9729
        $list = $this->getItemsForForm(true);
9730
9731
        foreach ($list as $row_zero) {
9732
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9733
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9734
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9735
                }
9736
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9737
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9738
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9739
            }
9740
        }
9741
9742
        $return .= "\n";
9743
        $sql = "SELECT * FROM $tbl_lp_item
9744
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9745
        $res = Database::query($sql);
9746
        while ($row = Database::fetch_array($res)) {
9747
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9748
                           WHERE
9749
                                c_id = ".$course_id." AND
9750
                                parent_item_id = ".$row['iid']."
9751
                           ORDER BY display_order ASC";
9752
            $res_parent = Database::query($sql_parent);
9753
            $i = 0;
9754
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9755
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9756
9757
            while ($row_parent = Database::fetch_array($res_parent)) {
9758
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9759
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9760
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9761
            }
9762
            $return .= "\n";
9763
        }
9764
9765
        $return .= "
9766
            function load_cbo(id) {
9767
                if (!id) {
9768
                    return false;
9769
                }
9770
9771
                var cbo = document.getElementById('previous');
9772
                for(var i = cbo.length - 1; i > 0; i--) {
9773
                    cbo.options[i] = null;
9774
                }
9775
9776
                var k=0;
9777
                for(var i = 1; i <= child_name[id].length; i++){
9778
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9779
                    option.style.paddingLeft = '40px';
9780
                    cbo.options[i] = option;
9781
                    k = i;
9782
                }
9783
9784
                cbo.options[k].selected = true;
9785
                $('#previous').selectpicker('refresh');
9786
            }";
9787
9788
        return $return;
9789
    }
9790
9791
    /**
9792
     * Display the form to allow moving an item.
9793
     *
9794
     * @param int $item_id Item ID
9795
     *
9796
     * @throws Exception
9797
     * @throws HTML_QuickForm_Error
9798
     *
9799
     * @return string HTML form
9800
     */
9801
    public function display_move_item($item_id)
9802
    {
9803
        $return = '';
9804
        if (is_numeric($item_id)) {
9805
            $item_id = (int) $item_id;
9806
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9807
9808
            $sql = "SELECT * FROM $tbl_lp_item
9809
                    WHERE iid = $item_id";
9810
            $res = Database::query($sql);
9811
            $row = Database::fetch_array($res);
9812
9813
            switch ($row['item_type']) {
9814
                case 'dir':
9815
                case 'asset':
9816
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9817
                    $return .= $this->display_item_form(
9818
                        $row['item_type'],
9819
                        get_lang('MoveCurrentChapter'),
9820
                        'move',
9821
                        $item_id,
9822
                        $row
9823
                    );
9824
                    break;
9825
                case TOOL_DOCUMENT:
9826
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9827
                    $return .= $this->display_document_form('move', $item_id, $row);
9828
                    break;
9829
                case TOOL_LINK:
9830
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9831
                    $return .= $this->display_link_form('move', $item_id, $row);
9832
                    break;
9833
                case TOOL_HOTPOTATOES:
9834
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9835
                    $return .= $this->display_link_form('move', $item_id, $row);
9836
                    break;
9837
                case TOOL_QUIZ:
9838
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9839
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9840
                    break;
9841
                case TOOL_STUDENTPUBLICATION:
9842
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9843
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9844
                    break;
9845
                case TOOL_FORUM:
9846
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9847
                    $return .= $this->display_forum_form('move', $item_id, $row);
9848
                    break;
9849
                case TOOL_THREAD:
9850
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9851
                    $return .= $this->display_forum_form('move', $item_id, $row);
9852
                    break;
9853
            }
9854
        }
9855
9856
        return $return;
9857
    }
9858
9859
    /**
9860
     * Return HTML form to allow prerequisites selection.
9861
     *
9862
     * @todo use FormValidator
9863
     *
9864
     * @param int Item ID
9865
     *
9866
     * @return string HTML form
9867
     */
9868
    public function display_item_prerequisites_form($item_id = 0)
9869
    {
9870
        $course_id = api_get_course_int_id();
9871
        $item_id = (int) $item_id;
9872
9873
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9874
9875
        /* Current prerequisite */
9876
        $sql = "SELECT * FROM $tbl_lp_item
9877
                WHERE iid = $item_id";
9878
        $result = Database::query($sql);
9879
        $row = Database::fetch_array($result);
9880
        $prerequisiteId = $row['prerequisite'];
9881
        $return = '<legend>';
9882
        $return .= get_lang('AddEditPrerequisites');
9883
        $return .= '</legend>';
9884
        $return .= '<form method="POST">';
9885
        $return .= '<div class="table-responsive">';
9886
        $return .= '<table class="table table-hover">';
9887
        $return .= '<thead>';
9888
        $return .= '<tr>';
9889
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9890
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9891
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9892
        $return .= '</tr>';
9893
        $return .= '</thead>';
9894
9895
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9896
        $return .= '<tbody>';
9897
        $return .= '<tr>';
9898
        $return .= '<td colspan="3">';
9899
        $return .= '<div class="radio learnpath"><label for="idNone">';
9900
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9901
        $return .= get_lang('None').'</label>';
9902
        $return .= '</div>';
9903
        $return .= '</tr>';
9904
9905
        $sql = "SELECT * FROM $tbl_lp_item
9906
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9907
        $result = Database::query($sql);
9908
9909
        $selectedMinScore = [];
9910
        $selectedMaxScore = [];
9911
        $masteryScore = [];
9912
        while ($row = Database::fetch_array($result)) {
9913
            if ($row['iid'] == $item_id) {
9914
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9915
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9916
            }
9917
            $masteryScore[$row['iid']] = $row['mastery_score'];
9918
        }
9919
9920
        $arrLP = $this->getItemsForForm();
9921
        $this->tree_array($arrLP);
9922
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9923
        unset($this->arrMenu);
9924
9925
        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...
9926
            $item = $arrLP[$i];
9927
9928
            if ($item['id'] == $item_id) {
9929
                break;
9930
            }
9931
9932
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9933
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9934
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9935
9936
            $return .= '<tr>';
9937
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9938
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9939
            $return .= '<label for="id'.$item['id'].'">';
9940
            $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'].'" />';
9941
9942
            $icon_name = str_replace(' ', '', $item['item_type']);
9943
9944
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9945
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9946
            } else {
9947
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9948
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9949
                } else {
9950
                    $return .= Display::return_icon('folder_document.png');
9951
                }
9952
            }
9953
9954
            $return .= $item['title'].'</label>';
9955
            $return .= '</div>';
9956
            $return .= '</td>';
9957
9958
            if ($item['item_type'] == TOOL_QUIZ) {
9959
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9960
                $lpItemObj = new LpItem($course_id, $item['id']);
9961
                $exercise = new Exercise($course_id);
9962
                $exercise->read($lpItemObj->path);
9963
                $lpItemObj->max_score = $exercise->get_max_score();
9964
                $lpItemObj->update();
9965
                $item['max_score'] = $lpItemObj->max_score;
9966
9967
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9968
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9969
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9970
                }
9971
9972
                $return .= '<td>';
9973
                $return .= '<input
9974
                    class="form-control"
9975
                    size="4" maxlength="3"
9976
                    name="min_'.$item['id'].'"
9977
                    type="number"
9978
                    min="0"
9979
                    step="any"
9980
                    max="'.$item['max_score'].'"
9981
                    value="'.$selectedMinScoreValue.'"
9982
                />';
9983
                $return .= '</td>';
9984
                $return .= '<td>';
9985
                $return .= '<input
9986
                    class="form-control"
9987
                    size="4"
9988
                    maxlength="3"
9989
                    name="max_'.$item['id'].'"
9990
                    type="number"
9991
                    min="0"
9992
                    step="any"
9993
                    max="'.$item['max_score'].'"
9994
                    value="'.$selectedMaxScoreValue.'"
9995
                />';
9996
                $return .= '</td>';
9997
            }
9998
9999
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10000
                $return .= '<td>';
10001
                $return .= '<input
10002
                    size="4"
10003
                    maxlength="3"
10004
                    name="min_'.$item['id'].'"
10005
                    type="number"
10006
                    min="0"
10007
                    step="any"
10008
                    max="'.$item['max_score'].'"
10009
                    value="'.$selectedMinScoreValue.'"
10010
                />';
10011
                $return .= '</td>';
10012
                $return .= '<td>';
10013
                $return .= '<input
10014
                    size="4"
10015
                    maxlength="3"
10016
                    name="max_'.$item['id'].'"
10017
                    type="number"
10018
                    min="0"
10019
                    step="any"
10020
                    max="'.$item['max_score'].'"
10021
                    value="'.$selectedMaxScoreValue.'"
10022
                />';
10023
                $return .= '</td>';
10024
            }
10025
            $return .= '</tr>';
10026
        }
10027
        $return .= '<tr>';
10028
        $return .= '</tr>';
10029
        $return .= '</tbody>';
10030
        $return .= '</table>';
10031
        $return .= '</div>';
10032
        $return .= '<div class="form-group">';
10033
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10034
            get_lang('ModifyPrerequisites').'</button>';
10035
        $return .= '</form>';
10036
10037
        return $return;
10038
    }
10039
10040
    /**
10041
     * Return HTML list to allow prerequisites selection for lp.
10042
     *
10043
     * @return string HTML form
10044
     */
10045
    public function display_lp_prerequisites_list()
10046
    {
10047
        $course_id = api_get_course_int_id();
10048
        $lp_id = $this->lp_id;
10049
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10050
10051
        // get current prerequisite
10052
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10053
        $result = Database::query($sql);
10054
        $row = Database::fetch_array($result);
10055
        $prerequisiteId = $row['prerequisite'];
10056
        $session_id = api_get_session_id();
10057
        $session_condition = api_get_session_condition($session_id, true, true);
10058
        $sql = "SELECT * FROM $tbl_lp
10059
                WHERE c_id = $course_id $session_condition
10060
                ORDER BY display_order ";
10061
        $rs = Database::query($sql);
10062
        $return = '';
10063
        $return .= '<select name="prerequisites" class="form-control">';
10064
        $return .= '<option value="0">'.get_lang('None').'</option>';
10065
        if (Database::num_rows($rs) > 0) {
10066
            while ($row = Database::fetch_array($rs)) {
10067
                if ($row['id'] == $lp_id) {
10068
                    continue;
10069
                }
10070
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10071
            }
10072
        }
10073
        $return .= '</select>';
10074
10075
        return $return;
10076
    }
10077
10078
    /**
10079
     * Creates a list with all the documents in it.
10080
     *
10081
     * @param bool $showInvisibleFiles
10082
     *
10083
     * @throws Exception
10084
     * @throws HTML_QuickForm_Error
10085
     *
10086
     * @return string
10087
     */
10088
    public function get_documents($showInvisibleFiles = false)
10089
    {
10090
        $course_info = api_get_course_info();
10091
        $sessionId = api_get_session_id();
10092
        $documentTree = DocumentManager::get_document_preview(
10093
            $course_info,
10094
            $this->lp_id,
10095
            null,
10096
            $sessionId,
10097
            true,
10098
            null,
10099
            null,
10100
            $showInvisibleFiles,
10101
            true
10102
        );
10103
10104
        $headers = [
10105
            get_lang('Files'),
10106
            get_lang('CreateTheDocument'),
10107
            get_lang('CreateReadOutText'),
10108
            get_lang('Upload'),
10109
        ];
10110
10111
        $form = new FormValidator(
10112
            'form_upload',
10113
            'POST',
10114
            $this->getCurrentBuildingModeURL(),
10115
            '',
10116
            ['enctype' => 'multipart/form-data']
10117
        );
10118
10119
        $folders = DocumentManager::get_all_document_folders(
10120
            api_get_course_info(),
10121
            0,
10122
            true
10123
        );
10124
10125
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10126
10127
        DocumentManager::build_directory_selector(
10128
            $folders,
10129
            $lpPathInfo['id'],
10130
            [],
10131
            true,
10132
            $form,
10133
            'directory_parent_id'
10134
        );
10135
10136
        $group = [
10137
            $form->createElement(
10138
                'radio',
10139
                'if_exists',
10140
                get_lang('UplWhatIfFileExists'),
10141
                get_lang('UplDoNothing'),
10142
                'nothing'
10143
            ),
10144
            $form->createElement(
10145
                'radio',
10146
                'if_exists',
10147
                null,
10148
                get_lang('UplOverwriteLong'),
10149
                'overwrite'
10150
            ),
10151
            $form->createElement(
10152
                'radio',
10153
                'if_exists',
10154
                null,
10155
                get_lang('UplRenameLong'),
10156
                'rename'
10157
            ),
10158
        ];
10159
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10160
10161
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10162
        $defaultFileExistsOption = 'rename';
10163
        if (!empty($fileExistsOption)) {
10164
            $defaultFileExistsOption = $fileExistsOption;
10165
        }
10166
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10167
10168
        // Check box options
10169
        $form->addElement(
10170
            'checkbox',
10171
            'unzip',
10172
            get_lang('Options'),
10173
            get_lang('Uncompress')
10174
        );
10175
10176
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10177
        $form->addMultipleUpload($url);
10178
        $new = $this->display_document_form('add', 0);
10179
        $frmReadOutText = $this->displayFrmReadOutText('add');
10180
        $tabs = Display::tabs(
10181
            $headers,
10182
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10183
            'subtab'
10184
        );
10185
10186
        return $tabs;
10187
    }
10188
10189
    /**
10190
     * Creates a list with all the exercises (quiz) in it.
10191
     *
10192
     * @return string
10193
     */
10194
    public function get_exercises()
10195
    {
10196
        $course_id = api_get_course_int_id();
10197
        $session_id = api_get_session_id();
10198
        $userInfo = api_get_user_info();
10199
10200
        // New for hotpotatoes.
10201
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10202
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10203
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10204
        $condition_session = api_get_session_condition($session_id, true, true);
10205
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10206
10207
        $activeCondition = ' active <> -1 ';
10208
        if ($setting) {
10209
            $activeCondition = ' active = 1 ';
10210
        }
10211
10212
        $categoryCondition = '';
10213
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10214
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10215
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10216
        }
10217
10218
        $keywordCondition = '';
10219
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10220
10221
        if (!empty($keyword)) {
10222
            $keyword = Database::escape_string($keyword);
10223
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10224
        }
10225
10226
        $sql_quiz = "SELECT * FROM $tbl_quiz
10227
                     WHERE
10228
                            c_id = $course_id AND
10229
                            $activeCondition
10230
                            $condition_session
10231
                            $categoryCondition
10232
                            $keywordCondition
10233
                     ORDER BY title ASC";
10234
10235
        $sql_hot = "SELECT * FROM $tbl_doc
10236
                    WHERE
10237
                        c_id = $course_id AND
10238
                        path LIKE '".$uploadPath."/%/%htm%'
10239
                        $condition_session
10240
                     ORDER BY id ASC";
10241
10242
        $res_quiz = Database::query($sql_quiz);
10243
        $res_hot = Database::query($sql_hot);
10244
10245
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10246
10247
        // Create a search-box
10248
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10249
        $form->addHidden('action', 'add_item');
10250
        $form->addHidden('type', 'step');
10251
        $form->addHidden('lp_id', $this->lp_id);
10252
        $form->addHidden('lp_build_selected', '2');
10253
10254
        $form->addCourseHiddenParams();
10255
        $form->addText(
10256
            'keyword',
10257
            get_lang('Search'),
10258
            false,
10259
            [
10260
                'aria-label' => get_lang('Search'),
10261
            ]
10262
        );
10263
10264
        if (api_get_configuration_value('allow_exercise_categories')) {
10265
            $manager = new ExerciseCategoryManager();
10266
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10267
            if (!empty($options)) {
10268
                $form->addSelect(
10269
                    'category_id',
10270
                    get_lang('Category'),
10271
                    $options,
10272
                    ['placeholder' => get_lang('SelectAnOption')]
10273
                );
10274
            }
10275
        }
10276
10277
        $form->addButtonSearch(get_lang('Search'));
10278
        $return = $form->returnForm();
10279
10280
        $return .= '<ul class="lp_resource">';
10281
10282
        $return .= '<li class="lp_resource_element">';
10283
        $return .= Display::return_icon('new_exercice.png');
10284
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10285
            get_lang('NewExercise').'</a>';
10286
        $return .= '</li>';
10287
10288
        $previewIcon = Display::return_icon(
10289
            'preview_view.png',
10290
            get_lang('Preview')
10291
        );
10292
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10293
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10294
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10295
10296
        // Display hotpotatoes
10297
        while ($row_hot = Database::fetch_array($res_hot)) {
10298
            $link = Display::url(
10299
                $previewIcon,
10300
                $exerciseUrl.'&file='.$row_hot['path'],
10301
                ['target' => '_blank']
10302
            );
10303
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10304
            $return .= '<a class="moved" href="#">';
10305
            $return .= Display::return_icon(
10306
                'move_everywhere.png',
10307
                get_lang('Move'),
10308
                [],
10309
                ICON_SIZE_TINY
10310
            );
10311
            $return .= '</a> ';
10312
            $return .= Display::return_icon('hotpotatoes_s.png');
10313
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10314
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10315
            $return .= '</li>';
10316
        }
10317
10318
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10319
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10320
            $title = strip_tags(
10321
                api_html_entity_decode($row_quiz['title'])
10322
            );
10323
10324
            $visibility = api_get_item_visibility(
10325
                ['real_id' => $course_id],
10326
                TOOL_QUIZ,
10327
                $row_quiz['iid'],
10328
                $session_id
10329
            );
10330
10331
            $link = Display::url(
10332
                $previewIcon,
10333
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10334
                ['target' => '_blank']
10335
            );
10336
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10337
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10338
            $return .= $quizIcon;
10339
            $sessionStar = api_get_session_image(
10340
                $row_quiz['session_id'],
10341
                $userInfo['status']
10342
            );
10343
            $return .= Display::url(
10344
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10345
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10346
                [
10347
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10348
                ]
10349
            );
10350
            $return .= '</li>';
10351
        }
10352
10353
        $return .= '</ul>';
10354
10355
        return $return;
10356
    }
10357
10358
    /**
10359
     * Creates a list with all the links in it.
10360
     *
10361
     * @return string
10362
     */
10363
    public function get_links()
10364
    {
10365
        $selfUrl = api_get_self();
10366
        $courseIdReq = api_get_cidreq();
10367
        $course = api_get_course_info();
10368
        $userInfo = api_get_user_info();
10369
10370
        $course_id = $course['real_id'];
10371
        $tbl_link = Database::get_course_table(TABLE_LINK);
10372
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10373
        $moveEverywhereIcon = Display::return_icon(
10374
            'move_everywhere.png',
10375
            get_lang('Move'),
10376
            [],
10377
            ICON_SIZE_TINY
10378
        );
10379
10380
        $session_id = api_get_session_id();
10381
        $condition_session = api_get_session_condition(
10382
            $session_id,
10383
            true,
10384
            true,
10385
            'link.session_id'
10386
        );
10387
10388
        $sql = "SELECT
10389
                    link.id as link_id,
10390
                    link.title as link_title,
10391
                    link.session_id as link_session_id,
10392
                    link.category_id as category_id,
10393
                    link_category.category_title as category_title
10394
                FROM $tbl_link as link
10395
                LEFT JOIN $linkCategoryTable as link_category
10396
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10397
                WHERE link.c_id = $course_id $condition_session
10398
                ORDER BY link_category.category_title ASC, link.title ASC";
10399
        $result = Database::query($sql);
10400
        $categorizedLinks = [];
10401
        $categories = [];
10402
10403
        while ($link = Database::fetch_array($result)) {
10404
            if (!$link['category_id']) {
10405
                $link['category_title'] = get_lang('Uncategorized');
10406
            }
10407
            $categories[$link['category_id']] = $link['category_title'];
10408
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10409
        }
10410
10411
        $linksHtmlCode =
10412
            '<script>
10413
            function toggle_tool(tool, id) {
10414
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10415
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10416
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10417
                } else {
10418
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10419
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10420
                }
10421
            }
10422
        </script>
10423
10424
        <ul class="lp_resource">
10425
            <li class="lp_resource_element">
10426
                '.Display::return_icon('linksnew.gif').'
10427
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10428
                get_lang('LinkAdd').'
10429
                </a>
10430
            </li>';
10431
10432
        foreach ($categorizedLinks as $categoryId => $links) {
10433
            $linkNodes = null;
10434
            foreach ($links as $key => $linkInfo) {
10435
                $title = $linkInfo['link_title'];
10436
                $linkSessionId = $linkInfo['link_session_id'];
10437
10438
                $link = Display::url(
10439
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10440
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10441
                    ['target' => '_blank']
10442
                );
10443
10444
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10445
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10446
                    $linkNodes .=
10447
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10448
                        <a class="moved" href="#">'.
10449
                            $moveEverywhereIcon.
10450
                        '</a>
10451
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10452
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10453
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10454
                        Security::remove_XSS($title).$sessionStar.$link.
10455
                        '</a>
10456
                    </li>';
10457
                }
10458
            }
10459
            $linksHtmlCode .=
10460
                '<li>
10461
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10462
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10463
                    align="absbottom" />
10464
                </a>
10465
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10466
            </li>
10467
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10468
        }
10469
        $linksHtmlCode .= '</ul>';
10470
10471
        return $linksHtmlCode;
10472
    }
10473
10474
    /**
10475
     * Creates a list with all the student publications in it.
10476
     *
10477
     * @return string
10478
     */
10479
    public function get_student_publications()
10480
    {
10481
        $return = '<ul class="lp_resource">';
10482
        $return .= '<li class="lp_resource_element">';
10483
        $return .= Display::return_icon('works_new.gif');
10484
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10485
            get_lang('AddAssignmentPage').'</a>';
10486
        $return .= '</li>';
10487
10488
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10489
        $works = getWorkListTeacher(0, 100, null, null, null);
10490
        if (!empty($works)) {
10491
            foreach ($works as $work) {
10492
                $link = Display::url(
10493
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10494
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10495
                    ['target' => '_blank']
10496
                );
10497
10498
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10499
                $return .= '<a class="moved" href="#">';
10500
                $return .= Display::return_icon(
10501
                    'move_everywhere.png',
10502
                    get_lang('Move'),
10503
                    [],
10504
                    ICON_SIZE_TINY
10505
                );
10506
                $return .= '</a> ';
10507
10508
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10509
                $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.'">'.
10510
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10511
                </a>';
10512
10513
                $return .= '</li>';
10514
            }
10515
        }
10516
10517
        $return .= '</ul>';
10518
10519
        return $return;
10520
    }
10521
10522
    /**
10523
     * Creates a list with all the forums in it.
10524
     *
10525
     * @return string
10526
     */
10527
    public function get_forums()
10528
    {
10529
        require_once '../forum/forumfunction.inc.php';
10530
10531
        $forumCategories = get_forum_categories();
10532
        $forumsInNoCategory = get_forums_in_category(0);
10533
        if (!empty($forumsInNoCategory)) {
10534
            $forumCategories = array_merge(
10535
                $forumCategories,
10536
                [
10537
                    [
10538
                        'cat_id' => 0,
10539
                        'session_id' => 0,
10540
                        'visibility' => 1,
10541
                        'cat_comment' => null,
10542
                    ],
10543
                ]
10544
            );
10545
        }
10546
10547
        $forumList = get_forums();
10548
        $a_forums = [];
10549
        foreach ($forumCategories as $forumCategory) {
10550
            // The forums in this category.
10551
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10552
            if (!empty($forumsInCategory)) {
10553
                foreach ($forumList as $forum) {
10554
                    if (isset($forum['forum_category']) &&
10555
                        $forum['forum_category'] == $forumCategory['cat_id']
10556
                    ) {
10557
                        $a_forums[] = $forum;
10558
                    }
10559
                }
10560
            }
10561
        }
10562
10563
        $return = '<ul class="lp_resource">';
10564
10565
        // First add link
10566
        $return .= '<li class="lp_resource_element">';
10567
        $return .= Display::return_icon('new_forum.png');
10568
        $return .= Display::url(
10569
            get_lang('CreateANewForum'),
10570
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10571
                'action' => 'add',
10572
                'content' => 'forum',
10573
                'lp_id' => $this->lp_id,
10574
            ]),
10575
            ['title' => get_lang('CreateANewForum')]
10576
        );
10577
        $return .= '</li>';
10578
10579
        $return .= '<script>
10580
            function toggle_forum(forum_id) {
10581
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10582
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10583
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10584
                } else {
10585
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10586
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10587
                }
10588
            }
10589
        </script>';
10590
10591
        foreach ($a_forums as $forum) {
10592
            if (!empty($forum['forum_id'])) {
10593
                $link = Display::url(
10594
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10595
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10596
                    ['target' => '_blank']
10597
                );
10598
10599
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10600
                $return .= '<a class="moved" href="#">';
10601
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10602
                $return .= ' </a>';
10603
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10604
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10605
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10606
                            </a>
10607
                            <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">'.
10608
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10609
10610
                $return .= '</li>';
10611
10612
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10613
                $a_threads = get_threads($forum['forum_id']);
10614
                if (is_array($a_threads)) {
10615
                    foreach ($a_threads as $thread) {
10616
                        $link = Display::url(
10617
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10618
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10619
                            ['target' => '_blank']
10620
                        );
10621
10622
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10623
                        $return .= '&nbsp;<a class="moved" href="#">';
10624
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10625
                        $return .= ' </a>';
10626
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10627
                        $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.'">'.
10628
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10629
                        $return .= '</li>';
10630
                    }
10631
                }
10632
                $return .= '</div>';
10633
            }
10634
        }
10635
        $return .= '</ul>';
10636
10637
        return $return;
10638
    }
10639
10640
    /**
10641
     * // TODO: The output encoding should be equal to the system encoding.
10642
     *
10643
     * Exports the learning path as a SCORM package. This is the main function that
10644
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10645
     * whole thing and returns the zip.
10646
     *
10647
     * This method needs to be called in PHP5, as it will fail with non-adequate
10648
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10649
     * you need to call it on a learnpath object.
10650
     *
10651
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10652
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10653
     * path has been modified, it should use the generic method here below.
10654
     *
10655
     * @return string Returns the zip package string, or null if error
10656
     */
10657
    public function scormExport()
10658
    {
10659
        api_set_more_memory_and_time_limits();
10660
10661
        $_course = api_get_course_info();
10662
        $course_id = $_course['real_id'];
10663
        // Create the zip handler (this will remain available throughout the method).
10664
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10665
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10666
        $temp_dir_short = uniqid('scorm_export', true);
10667
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10668
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10669
        $zip_folder = new PclZip($temp_zip_file);
10670
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10671
        $root_path = $main_path = api_get_path(SYS_PATH);
10672
        $files_cleanup = [];
10673
10674
        // Place to temporarily stash the zip file.
10675
        // create the temp dir if it doesn't exist
10676
        // or do a cleanup before creating the zip file.
10677
        if (!is_dir($temp_zip_dir)) {
10678
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10679
        } else {
10680
            // Cleanup: Check the temp dir for old files and delete them.
10681
            $handle = opendir($temp_zip_dir);
10682
            while (false !== ($file = readdir($handle))) {
10683
                if ($file != '.' && $file != '..') {
10684
                    unlink("$temp_zip_dir/$file");
10685
                }
10686
            }
10687
            closedir($handle);
10688
        }
10689
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10690
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10691
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10692
        ) {
10693
            // Remove the possible . at the end of the path.
10694
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10695
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10696
            mkdir(
10697
                $dest_path_to_scorm_folder,
10698
                api_get_permissions_for_new_directories(),
10699
                true
10700
            );
10701
            copyr(
10702
                $current_course_path.'/scorm/'.$this->path,
10703
                $dest_path_to_scorm_folder,
10704
                ['imsmanifest'],
10705
                $zip_files
10706
            );
10707
        }
10708
10709
        // Build a dummy imsmanifest structure.
10710
        // Do not add to the zip yet (we still need it).
10711
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10712
        // Aggregation Model official document, section "2.3 Content Packaging".
10713
        // We are going to build a UTF-8 encoded manifest.
10714
        // Later we will recode it to the desired (and supported) encoding.
10715
        $xmldoc = new DOMDocument('1.0');
10716
        $root = $xmldoc->createElement('manifest');
10717
        $root->setAttribute('identifier', 'SingleCourseManifest');
10718
        $root->setAttribute('version', '1.1');
10719
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10720
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10721
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10722
        $root->setAttribute(
10723
            'xsi:schemaLocation',
10724
            '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'
10725
        );
10726
        // Build mandatory sub-root container elements.
10727
        $metadata = $xmldoc->createElement('metadata');
10728
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10729
        $metadata->appendChild($md_schema);
10730
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10731
        $metadata->appendChild($md_schemaversion);
10732
        $root->appendChild($metadata);
10733
10734
        $organizations = $xmldoc->createElement('organizations');
10735
        $resources = $xmldoc->createElement('resources');
10736
10737
        // Build the only organization we will use in building our learnpaths.
10738
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10739
        $organization = $xmldoc->createElement('organization');
10740
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10741
        // To set the title of the SCORM entity (=organization), we take the name given
10742
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10743
        // learning path charset) as it is the encoding that defines how it is stored
10744
        // in the database. Then we convert it to HTML entities again as the "&" character
10745
        // alone is not authorized in XML (must be &amp;).
10746
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10747
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10748
        $organization->appendChild($org_title);
10749
        $folder_name = 'document';
10750
10751
        // Removes the learning_path/scorm_folder path when exporting see #4841
10752
        $path_to_remove = '';
10753
        $path_to_replace = '';
10754
        $result = $this->generate_lp_folder($_course);
10755
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10756
            $path_to_remove = 'document'.$result['dir'];
10757
            $path_to_replace = $folder_name.'/';
10758
        }
10759
10760
        // Fixes chamilo scorm exports
10761
        if ($this->ref === 'chamilo_scorm_export') {
10762
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10763
        }
10764
10765
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10766
        $link_updates = [];
10767
        $links_to_create = [];
10768
        foreach ($this->ordered_items as $index => $itemId) {
10769
            /** @var learnpathItem $item */
10770
            $item = $this->items[$itemId];
10771
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10772
                // Get included documents from this item.
10773
                if ($item->type === 'sco') {
10774
                    $inc_docs = $item->get_resources_from_source(
10775
                        null,
10776
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10777
                    );
10778
                } else {
10779
                    $inc_docs = $item->get_resources_from_source();
10780
                }
10781
10782
                // Give a child element <item> to the <organization> element.
10783
                $my_item_id = $item->get_id();
10784
                $my_item = $xmldoc->createElement('item');
10785
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10786
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10787
                $my_item->setAttribute('isvisible', 'true');
10788
                // Give a child element <title> to the <item> element.
10789
                $my_title = $xmldoc->createElement(
10790
                    'title',
10791
                    htmlspecialchars(
10792
                        api_utf8_encode($item->get_title()),
10793
                        ENT_QUOTES,
10794
                        'UTF-8'
10795
                    )
10796
                );
10797
                $my_item->appendChild($my_title);
10798
                // Give a child element <adlcp:prerequisites> to the <item> element.
10799
                $my_prereqs = $xmldoc->createElement(
10800
                    'adlcp:prerequisites',
10801
                    $this->get_scorm_prereq_string($my_item_id)
10802
                );
10803
                $my_prereqs->setAttribute('type', 'aicc_script');
10804
                $my_item->appendChild($my_prereqs);
10805
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10806
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10807
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10808
                //$xmldoc->createElement('adlcp:timelimitaction','');
10809
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10810
                //$xmldoc->createElement('adlcp:datafromlms','');
10811
                // Give a child element <adlcp:masteryscore> to the <item> element.
10812
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10813
                $my_item->appendChild($my_masteryscore);
10814
10815
                // Attach this item to the organization element or hits parent if there is one.
10816
                if (!empty($item->parent) && $item->parent != 0) {
10817
                    $children = $organization->childNodes;
10818
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10819
                    if (is_object($possible_parent)) {
10820
                        $possible_parent->appendChild($my_item);
10821
                    } else {
10822
                        if ($this->debug > 0) {
10823
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10824
                        }
10825
                    }
10826
                } else {
10827
                    if ($this->debug > 0) {
10828
                        error_log('No parent');
10829
                    }
10830
                    $organization->appendChild($my_item);
10831
                }
10832
10833
                // Get the path of the file(s) from the course directory root.
10834
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10835
                $my_xml_file_path = $my_file_path;
10836
                if (!empty($path_to_remove)) {
10837
                    // From docs
10838
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10839
10840
                    // From quiz
10841
                    if ($this->ref === 'chamilo_scorm_export') {
10842
                        $path_to_remove = 'scorm/'.$this->path.'/';
10843
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10844
                    }
10845
                }
10846
10847
                $my_sub_dir = dirname($my_file_path);
10848
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10849
                $my_xml_sub_dir = $my_sub_dir;
10850
                // Give a <resource> child to the <resources> element
10851
                $my_resource = $xmldoc->createElement('resource');
10852
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10853
                $my_resource->setAttribute('type', 'webcontent');
10854
                $my_resource->setAttribute('href', $my_xml_file_path);
10855
                // adlcp:scormtype can be either 'sco' or 'asset'.
10856
                if ($item->type === 'sco') {
10857
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10858
                } else {
10859
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10860
                }
10861
                // xml:base is the base directory to find the files declared in this resource.
10862
                $my_resource->setAttribute('xml:base', '');
10863
                // Give a <file> child to the <resource> element.
10864
                $my_file = $xmldoc->createElement('file');
10865
                $my_file->setAttribute('href', $my_xml_file_path);
10866
                $my_resource->appendChild($my_file);
10867
10868
                // Dependency to other files - not yet supported.
10869
                $i = 1;
10870
                if ($inc_docs) {
10871
                    foreach ($inc_docs as $doc_info) {
10872
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10873
                            continue;
10874
                        }
10875
                        $my_dep = $xmldoc->createElement('resource');
10876
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10877
                        $my_dep->setAttribute('identifier', $res_id);
10878
                        $my_dep->setAttribute('type', 'webcontent');
10879
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10880
                        $my_dep_file = $xmldoc->createElement('file');
10881
                        // Check type of URL.
10882
                        if ($doc_info[1] == 'remote') {
10883
                            // Remote file. Save url as is.
10884
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10885
                            $my_dep->setAttribute('xml:base', '');
10886
                        } elseif ($doc_info[1] === 'local') {
10887
                            switch ($doc_info[2]) {
10888
                                case 'url':
10889
                                    // Local URL - save path as url for now, don't zip file.
10890
                                    $abs_path = api_get_path(SYS_PATH).
10891
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10892
                                    $current_dir = dirname($abs_path);
10893
                                    $current_dir = str_replace('\\', '/', $current_dir);
10894
                                    $file_path = realpath($abs_path);
10895
                                    $file_path = str_replace('\\', '/', $file_path);
10896
                                    $my_dep_file->setAttribute('href', $file_path);
10897
                                    $my_dep->setAttribute('xml:base', '');
10898
                                    if (strstr($file_path, $main_path) !== false) {
10899
                                        // The calculated real path is really inside Chamilo's root path.
10900
                                        // Reduce file path to what's under the DocumentRoot.
10901
                                        $replace = $file_path;
10902
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10903
                                        $destinationFile = $file_path;
10904
10905
                                        if (strstr($file_path, 'upload/users') !== false) {
10906
                                            $pos = strpos($file_path, 'my_files/');
10907
                                            if ($pos !== false) {
10908
                                                $onlyDirectory = str_replace(
10909
                                                    'upload/users/',
10910
                                                    '',
10911
                                                    substr($file_path, $pos, strlen($file_path))
10912
                                                );
10913
                                            }
10914
                                            $replace = $onlyDirectory;
10915
                                            $destinationFile = $replace;
10916
                                        }
10917
                                        $zip_files_abs[] = $file_path;
10918
                                        $link_updates[$my_file_path][] = [
10919
                                            'orig' => $doc_info[0],
10920
                                            'dest' => $destinationFile,
10921
                                            'replace' => $replace,
10922
                                        ];
10923
                                        $my_dep_file->setAttribute('href', $file_path);
10924
                                        $my_dep->setAttribute('xml:base', '');
10925
                                    } elseif (empty($file_path)) {
10926
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10927
                                        $file_path = str_replace('//', '/', $file_path);
10928
                                        if (file_exists($file_path)) {
10929
                                            // We get the relative path.
10930
                                            $file_path = substr($file_path, strlen($current_dir));
10931
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10932
                                            $link_updates[$my_file_path][] = [
10933
                                                'orig' => $doc_info[0],
10934
                                                'dest' => $file_path,
10935
                                            ];
10936
                                            $my_dep_file->setAttribute('href', $file_path);
10937
                                            $my_dep->setAttribute('xml:base', '');
10938
                                        }
10939
                                    }
10940
                                    break;
10941
                                case 'abs':
10942
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10943
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10944
                                    $my_dep->setAttribute('xml:base', '');
10945
10946
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10947
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10948
                                    $abs_img_path_without_subdir = $doc_info[0];
10949
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10950
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10951
                                    if ($pos === 0) {
10952
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10953
                                    }
10954
10955
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10956
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10957
10958
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10959
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10960
                                    // Check if the current document is in that path.
10961
                                    if (strstr($file_path, $cur_path) !== false) {
10962
                                        $destinationFile = substr($file_path, strlen($cur_path));
10963
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10964
10965
                                        $fileToTest = $cur_path.$my_file_path;
10966
                                        if (!empty($path_to_remove)) {
10967
                                            $fileToTest = str_replace(
10968
                                                $path_to_remove.'/',
10969
                                                $path_to_replace,
10970
                                                $cur_path.$my_file_path
10971
                                            );
10972
                                        }
10973
10974
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10975
10976
                                        // Put the current document in the zip (this array is the array
10977
                                        // that will manage documents already in the course folder - relative).
10978
                                        $zip_files[] = $filePathNoCoursePart;
10979
                                        // Update the links to the current document in the
10980
                                        // containing document (make them relative).
10981
                                        $link_updates[$my_file_path][] = [
10982
                                            'orig' => $doc_info[0],
10983
                                            'dest' => $destinationFile,
10984
                                            'replace' => $relative_path,
10985
                                        ];
10986
10987
                                        $my_dep_file->setAttribute('href', $file_path);
10988
                                        $my_dep->setAttribute('xml:base', '');
10989
                                    } elseif (strstr($file_path, $main_path) !== false) {
10990
                                        // The calculated real path is really inside Chamilo's root path.
10991
                                        // Reduce file path to what's under the DocumentRoot.
10992
                                        $file_path = substr($file_path, strlen($root_path));
10993
                                        $zip_files_abs[] = $file_path;
10994
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10995
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10996
                                        $my_dep->setAttribute('xml:base', '');
10997
                                    } elseif (empty($file_path)) {
10998
                                        // Probably this is an image inside "/main" directory
10999
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11000
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11001
11002
                                        if (file_exists($file_path)) {
11003
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11004
                                                // We get the relative path.
11005
                                                $pos = strpos($file_path, 'main/default_course_document/');
11006
                                                if ($pos !== false) {
11007
                                                    $onlyDirectory = str_replace(
11008
                                                        'main/default_course_document/',
11009
                                                        '',
11010
                                                        substr($file_path, $pos, strlen($file_path))
11011
                                                    );
11012
                                                }
11013
11014
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11015
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11016
                                                $zip_files_abs[] = $fileAbs;
11017
                                                $link_updates[$my_file_path][] = [
11018
                                                    'orig' => $doc_info[0],
11019
                                                    'dest' => $destinationFile,
11020
                                                ];
11021
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11022
                                                $my_dep->setAttribute('xml:base', '');
11023
                                            }
11024
                                        }
11025
                                    }
11026
                                    break;
11027
                                case 'rel':
11028
                                    // Path relative to the current document.
11029
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11030
                                    if (substr($doc_info[0], 0, 2) === '..') {
11031
                                        // Relative path going up.
11032
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11033
                                        $current_dir = str_replace('\\', '/', $current_dir);
11034
                                        $file_path = realpath($current_dir.$doc_info[0]);
11035
                                        $file_path = str_replace('\\', '/', $file_path);
11036
                                        if (strstr($file_path, $main_path) !== false) {
11037
                                            // The calculated real path is really inside Chamilo's root path.
11038
                                            // Reduce file path to what's under the DocumentRoot.
11039
                                            $file_path = substr($file_path, strlen($root_path));
11040
                                            $zip_files_abs[] = $file_path;
11041
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11042
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11043
                                            $my_dep->setAttribute('xml:base', '');
11044
                                        }
11045
                                    } else {
11046
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11047
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11048
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11049
                                    }
11050
                                    break;
11051
                                default:
11052
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11053
                                    $my_dep->setAttribute('xml:base', '');
11054
                                    break;
11055
                            }
11056
                        }
11057
                        $my_dep->appendChild($my_dep_file);
11058
                        $resources->appendChild($my_dep);
11059
                        $dependency = $xmldoc->createElement('dependency');
11060
                        $dependency->setAttribute('identifierref', $res_id);
11061
                        $my_resource->appendChild($dependency);
11062
                        $i++;
11063
                    }
11064
                }
11065
                $resources->appendChild($my_resource);
11066
                $zip_files[] = $my_file_path;
11067
            } else {
11068
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11069
                switch ($item->type) {
11070
                    case TOOL_LINK:
11071
                        $my_item = $xmldoc->createElement('item');
11072
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11073
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11074
                        $my_item->setAttribute('isvisible', 'true');
11075
                        // Give a child element <title> to the <item> element.
11076
                        $my_title = $xmldoc->createElement(
11077
                            'title',
11078
                            htmlspecialchars(
11079
                                api_utf8_encode($item->get_title()),
11080
                                ENT_QUOTES,
11081
                                'UTF-8'
11082
                            )
11083
                        );
11084
                        $my_item->appendChild($my_title);
11085
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11086
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11087
                        $my_prereqs->setAttribute('type', 'aicc_script');
11088
                        $my_item->appendChild($my_prereqs);
11089
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11090
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11091
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11092
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11093
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11094
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11095
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11096
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11097
                        $my_item->appendChild($my_masteryscore);
11098
11099
                        // Attach this item to the organization element or its parent if there is one.
11100
                        if (!empty($item->parent) && $item->parent != 0) {
11101
                            $children = $organization->childNodes;
11102
                            for ($i = 0; $i < $children->length; $i++) {
11103
                                $item_temp = $children->item($i);
11104
                                if ($item_temp->nodeName == 'item') {
11105
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11106
                                        $item_temp->appendChild($my_item);
11107
                                    }
11108
                                }
11109
                            }
11110
                        } else {
11111
                            $organization->appendChild($my_item);
11112
                        }
11113
11114
                        $my_file_path = 'link_'.$item->get_id().'.html';
11115
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11116
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11117
                        $rs = Database::query($sql);
11118
                        if ($link = Database::fetch_array($rs)) {
11119
                            $url = $link['url'];
11120
                            $title = stripslashes($link['title']);
11121
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11122
                            $my_xml_file_path = $my_file_path;
11123
                            $my_sub_dir = dirname($my_file_path);
11124
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11125
                            $my_xml_sub_dir = $my_sub_dir;
11126
                            // Give a <resource> child to the <resources> element.
11127
                            $my_resource = $xmldoc->createElement('resource');
11128
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11129
                            $my_resource->setAttribute('type', 'webcontent');
11130
                            $my_resource->setAttribute('href', $my_xml_file_path);
11131
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11132
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11133
                            // xml:base is the base directory to find the files declared in this resource.
11134
                            $my_resource->setAttribute('xml:base', '');
11135
                            // give a <file> child to the <resource> element.
11136
                            $my_file = $xmldoc->createElement('file');
11137
                            $my_file->setAttribute('href', $my_xml_file_path);
11138
                            $my_resource->appendChild($my_file);
11139
                            $resources->appendChild($my_resource);
11140
                        }
11141
                        break;
11142
                    case TOOL_QUIZ:
11143
                        $exe_id = $item->path;
11144
                        // Should be using ref when everything will be cleaned up in this regard.
11145
                        $exe = new Exercise();
11146
                        $exe->read($exe_id);
11147
                        $my_item = $xmldoc->createElement('item');
11148
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11149
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11150
                        $my_item->setAttribute('isvisible', 'true');
11151
                        // Give a child element <title> to the <item> element.
11152
                        $my_title = $xmldoc->createElement(
11153
                            'title',
11154
                            htmlspecialchars(
11155
                                api_utf8_encode($item->get_title()),
11156
                                ENT_QUOTES,
11157
                                'UTF-8'
11158
                            )
11159
                        );
11160
                        $my_item->appendChild($my_title);
11161
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11162
                        $my_item->appendChild($my_max_score);
11163
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11164
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11165
                        $my_prereqs->setAttribute('type', 'aicc_script');
11166
                        $my_item->appendChild($my_prereqs);
11167
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11168
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11169
                        $my_item->appendChild($my_masteryscore);
11170
11171
                        // Attach this item to the organization element or hits parent if there is one.
11172
                        if (!empty($item->parent) && $item->parent != 0) {
11173
                            $children = $organization->childNodes;
11174
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11175
                            if ($possible_parent) {
11176
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11177
                                    $possible_parent->appendChild($my_item);
11178
                                }
11179
                            }
11180
                        } else {
11181
                            $organization->appendChild($my_item);
11182
                        }
11183
11184
                        // Get the path of the file(s) from the course directory root
11185
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11186
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11187
                        // Write the contents of the exported exercise into a (big) html file
11188
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11189
                        $scormExercise = new ScormExercise($exe, true);
11190
                        $contents = $scormExercise->export();
11191
11192
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11193
                        $res = file_put_contents($tmp_file_path, $contents);
11194
                        if ($res === false) {
11195
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11196
                        }
11197
                        $files_cleanup[] = $tmp_file_path;
11198
                        $my_xml_file_path = $my_file_path;
11199
                        $my_sub_dir = dirname($my_file_path);
11200
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11201
                        $my_xml_sub_dir = $my_sub_dir;
11202
                        // Give a <resource> child to the <resources> element.
11203
                        $my_resource = $xmldoc->createElement('resource');
11204
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11205
                        $my_resource->setAttribute('type', 'webcontent');
11206
                        $my_resource->setAttribute('href', $my_xml_file_path);
11207
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11208
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11209
                        // xml:base is the base directory to find the files declared in this resource.
11210
                        $my_resource->setAttribute('xml:base', '');
11211
                        // Give a <file> child to the <resource> element.
11212
                        $my_file = $xmldoc->createElement('file');
11213
                        $my_file->setAttribute('href', $my_xml_file_path);
11214
                        $my_resource->appendChild($my_file);
11215
11216
                        // Get included docs.
11217
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11218
11219
                        // Dependency to other files - not yet supported.
11220
                        $i = 1;
11221
                        foreach ($inc_docs as $doc_info) {
11222
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11223
                                continue;
11224
                            }
11225
                            $my_dep = $xmldoc->createElement('resource');
11226
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11227
                            $my_dep->setAttribute('identifier', $res_id);
11228
                            $my_dep->setAttribute('type', 'webcontent');
11229
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11230
                            $my_dep_file = $xmldoc->createElement('file');
11231
                            // Check type of URL.
11232
                            if ($doc_info[1] == 'remote') {
11233
                                // Remote file. Save url as is.
11234
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11235
                                $my_dep->setAttribute('xml:base', '');
11236
                            } elseif ($doc_info[1] == 'local') {
11237
                                switch ($doc_info[2]) {
11238
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11239
                                        // Save file but as local file (retrieve from URL).
11240
                                        $abs_path = api_get_path(SYS_PATH).
11241
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11242
                                        $current_dir = dirname($abs_path);
11243
                                        $current_dir = str_replace('\\', '/', $current_dir);
11244
                                        $file_path = realpath($abs_path);
11245
                                        $file_path = str_replace('\\', '/', $file_path);
11246
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11247
                                        $my_dep->setAttribute('xml:base', '');
11248
                                        if (strstr($file_path, $main_path) !== false) {
11249
                                            // The calculated real path is really inside the chamilo root path.
11250
                                            // Reduce file path to what's under the DocumentRoot.
11251
                                            $file_path = substr($file_path, strlen($root_path));
11252
                                            $zip_files_abs[] = $file_path;
11253
                                            $link_updates[$my_file_path][] = [
11254
                                                'orig' => $doc_info[0],
11255
                                                'dest' => 'document/'.$file_path,
11256
                                            ];
11257
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11258
                                            $my_dep->setAttribute('xml:base', '');
11259
                                        } elseif (empty($file_path)) {
11260
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11261
                                            $file_path = str_replace('//', '/', $file_path);
11262
                                            if (file_exists($file_path)) {
11263
                                                $file_path = substr($file_path, strlen($current_dir));
11264
                                                // We get the relative path.
11265
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11266
                                                $link_updates[$my_file_path][] = [
11267
                                                    'orig' => $doc_info[0],
11268
                                                    'dest' => 'document/'.$file_path,
11269
                                                ];
11270
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11271
                                                $my_dep->setAttribute('xml:base', '');
11272
                                            }
11273
                                        }
11274
                                        break;
11275
                                    case 'abs':
11276
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11277
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11278
                                        $current_dir = str_replace('\\', '/', $current_dir);
11279
                                        $file_path = realpath($doc_info[0]);
11280
                                        $file_path = str_replace('\\', '/', $file_path);
11281
                                        $my_dep_file->setAttribute('href', $file_path);
11282
                                        $my_dep->setAttribute('xml:base', '');
11283
11284
                                        if (strstr($file_path, $main_path) !== false) {
11285
                                            // The calculated real path is really inside the chamilo root path.
11286
                                            // Reduce file path to what's under the DocumentRoot.
11287
                                            $file_path = substr($file_path, strlen($root_path));
11288
                                            $zip_files_abs[] = $file_path;
11289
                                            $link_updates[$my_file_path][] = [
11290
                                                'orig' => $doc_info[0],
11291
                                                'dest' => $file_path,
11292
                                            ];
11293
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11294
                                            $my_dep->setAttribute('xml:base', '');
11295
                                        } elseif (empty($file_path)) {
11296
                                            $docSysPartPath = str_replace(
11297
                                                api_get_path(REL_COURSE_PATH),
11298
                                                '',
11299
                                                $doc_info[0]
11300
                                            );
11301
11302
                                            $docSysPartPathNoCourseCode = str_replace(
11303
                                                $_course['directory'].'/',
11304
                                                '',
11305
                                                $docSysPartPath
11306
                                            );
11307
11308
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11309
                                            if (file_exists($docSysPath)) {
11310
                                                $file_path = $docSysPartPathNoCourseCode;
11311
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11312
                                                $link_updates[$my_file_path][] = [
11313
                                                    'orig' => $doc_info[0],
11314
                                                    'dest' => $file_path,
11315
                                                ];
11316
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11317
                                                $my_dep->setAttribute('xml:base', '');
11318
                                            }
11319
                                        }
11320
                                        break;
11321
                                    case 'rel':
11322
                                        // Path relative to the current document. Save xml:base as current document's
11323
                                        // directory and save file in zip as subdir.file_path
11324
                                        if (substr($doc_info[0], 0, 2) === '..') {
11325
                                            // Relative path going up.
11326
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11327
                                            $current_dir = str_replace('\\', '/', $current_dir);
11328
                                            $file_path = realpath($current_dir.$doc_info[0]);
11329
                                            $file_path = str_replace('\\', '/', $file_path);
11330
                                            if (strstr($file_path, $main_path) !== false) {
11331
                                                // The calculated real path is really inside Chamilo's root path.
11332
                                                // Reduce file path to what's under the DocumentRoot.
11333
11334
                                                $file_path = substr($file_path, strlen($root_path));
11335
                                                $file_path_dest = $file_path;
11336
11337
                                                // File path is courses/CHAMILO/document/....
11338
                                                $info_file_path = explode('/', $file_path);
11339
                                                if ($info_file_path[0] == 'courses') {
11340
                                                    // Add character "/" in file path.
11341
                                                    $file_path_dest = 'document/'.$file_path;
11342
                                                }
11343
                                                $zip_files_abs[] = $file_path;
11344
11345
                                                $link_updates[$my_file_path][] = [
11346
                                                    'orig' => $doc_info[0],
11347
                                                    'dest' => $file_path_dest,
11348
                                                ];
11349
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11350
                                                $my_dep->setAttribute('xml:base', '');
11351
                                            }
11352
                                        } else {
11353
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11354
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11355
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11356
                                        }
11357
                                        break;
11358
                                    default:
11359
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11360
                                        $my_dep->setAttribute('xml:base', '');
11361
                                        break;
11362
                                }
11363
                            }
11364
                            $my_dep->appendChild($my_dep_file);
11365
                            $resources->appendChild($my_dep);
11366
                            $dependency = $xmldoc->createElement('dependency');
11367
                            $dependency->setAttribute('identifierref', $res_id);
11368
                            $my_resource->appendChild($dependency);
11369
                            $i++;
11370
                        }
11371
                        $resources->appendChild($my_resource);
11372
                        $zip_files[] = $my_file_path;
11373
                        break;
11374
                    default:
11375
                        // Get the path of the file(s) from the course directory root
11376
                        $my_file_path = 'non_exportable.html';
11377
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11378
                        $my_xml_file_path = $my_file_path;
11379
                        $my_sub_dir = dirname($my_file_path);
11380
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11381
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11382
                        $my_xml_sub_dir = $my_sub_dir;
11383
                        // Give a <resource> child to the <resources> element.
11384
                        $my_resource = $xmldoc->createElement('resource');
11385
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11386
                        $my_resource->setAttribute('type', 'webcontent');
11387
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11388
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11389
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11390
                        // xml:base is the base directory to find the files declared in this resource.
11391
                        $my_resource->setAttribute('xml:base', '');
11392
                        // Give a <file> child to the <resource> element.
11393
                        $my_file = $xmldoc->createElement('file');
11394
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11395
                        $my_resource->appendChild($my_file);
11396
                        $resources->appendChild($my_resource);
11397
                        break;
11398
                }
11399
            }
11400
        }
11401
        $organizations->appendChild($organization);
11402
        $root->appendChild($organizations);
11403
        $root->appendChild($resources);
11404
        $xmldoc->appendChild($root);
11405
11406
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11407
11408
        // then add the file to the zip, then destroy the file (this is done automatically).
11409
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11410
        foreach ($zip_files as $file_path) {
11411
            if (empty($file_path)) {
11412
                continue;
11413
            }
11414
11415
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11416
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11417
11418
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11419
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11420
            }
11421
11422
            $this->create_path($dest_file);
11423
            @copy($filePath, $dest_file);
11424
11425
            // Check if the file needs a link update.
11426
            if (in_array($file_path, array_keys($link_updates))) {
11427
                $string = file_get_contents($dest_file);
11428
                unlink($dest_file);
11429
                foreach ($link_updates[$file_path] as $old_new) {
11430
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11431
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11432
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11433
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11434
                    if (substr($old_new['dest'], -3) === 'flv' &&
11435
                        substr($old_new['dest'], 0, 5) === 'main/'
11436
                    ) {
11437
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11438
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11439
                        substr($old_new['dest'], 0, 6) === 'video/'
11440
                    ) {
11441
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11442
                    }
11443
11444
                    // Fix to avoid problems with default_course_document
11445
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11446
                        $newDestination = $old_new['dest'];
11447
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11448
                            $newDestination = $old_new['replace'];
11449
                        }
11450
                    } else {
11451
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11452
                    }
11453
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11454
11455
                    // Add files inside the HTMLs
11456
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11457
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11458
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11459
                        copy(
11460
                            $sys_course_path.$new_path,
11461
                            $destinationFile
11462
                        );
11463
                    }
11464
                }
11465
                file_put_contents($dest_file, $string);
11466
            }
11467
11468
            if (file_exists($filePath) && $copyAll) {
11469
                $extension = $this->get_extension($filePath);
11470
                if (in_array($extension, ['html', 'html'])) {
11471
                    $containerOrigin = dirname($filePath);
11472
                    $containerDestination = dirname($dest_file);
11473
11474
                    $finder = new Finder();
11475
                    $finder->files()->in($containerOrigin)
11476
                        ->notName('*_DELETED_*')
11477
                        ->exclude('share_folder')
11478
                        ->exclude('chat_files')
11479
                        ->exclude('certificates')
11480
                    ;
11481
11482
                    if (is_dir($containerOrigin) &&
11483
                        is_dir($containerDestination)
11484
                    ) {
11485
                        $fs = new Filesystem();
11486
                        $fs->mirror(
11487
                            $containerOrigin,
11488
                            $containerDestination,
11489
                            $finder
11490
                        );
11491
                    }
11492
                }
11493
            }
11494
        }
11495
11496
        foreach ($zip_files_abs as $file_path) {
11497
            if (empty($file_path)) {
11498
                continue;
11499
            }
11500
11501
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11502
                continue;
11503
            }
11504
11505
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11506
            if (strstr($file_path, 'upload/users') !== false) {
11507
                $pos = strpos($file_path, 'my_files/');
11508
                if ($pos !== false) {
11509
                    $onlyDirectory = str_replace(
11510
                        'upload/users/',
11511
                        '',
11512
                        substr($file_path, $pos, strlen($file_path))
11513
                    );
11514
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11515
                }
11516
            }
11517
11518
            if (strstr($file_path, 'default_course_document/') !== false) {
11519
                $replace = str_replace('/main', '', $file_path);
11520
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11521
            }
11522
11523
            if (empty($dest_file)) {
11524
                continue;
11525
            }
11526
11527
            $this->create_path($dest_file);
11528
            copy($main_path.$file_path, $dest_file);
11529
            // Check if the file needs a link update.
11530
            if (in_array($file_path, array_keys($link_updates))) {
11531
                $string = file_get_contents($dest_file);
11532
                unlink($dest_file);
11533
                foreach ($link_updates[$file_path] as $old_new) {
11534
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11535
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11536
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11537
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11538
                    if (substr($old_new['dest'], -3) == 'flv' &&
11539
                        substr($old_new['dest'], 0, 5) == 'main/'
11540
                    ) {
11541
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11542
                    }
11543
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11544
                }
11545
                file_put_contents($dest_file, $string);
11546
            }
11547
        }
11548
11549
        if (is_array($links_to_create)) {
11550
            foreach ($links_to_create as $file => $link) {
11551
                $content = '<!DOCTYPE html><head>
11552
                            <meta charset="'.api_get_language_isocode().'" />
11553
                            <title>'.$link['title'].'</title>
11554
                            </head>
11555
                            <body dir="'.api_get_text_direction().'">
11556
                            <div style="text-align:center">
11557
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11558
                            </body>
11559
                            </html>';
11560
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11561
            }
11562
        }
11563
11564
        // Add non exportable message explanation.
11565
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11566
        $file_content = '<!DOCTYPE html><head>
11567
                        <meta charset="'.api_get_language_isocode().'" />
11568
                        <title>'.$lang_not_exportable.'</title>
11569
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11570
                        </head>
11571
                        <body dir="'.api_get_text_direction().'">';
11572
        $file_content .=
11573
            <<<EOD
11574
                    <style>
11575
            .error-message {
11576
                font-family: arial, verdana, helvetica, sans-serif;
11577
                border-width: 1px;
11578
                border-style: solid;
11579
                left: 50%;
11580
                margin: 10px auto;
11581
                min-height: 30px;
11582
                padding: 5px;
11583
                right: 50%;
11584
                width: 500px;
11585
                background-color: #FFD1D1;
11586
                border-color: #FF0000;
11587
                color: #000;
11588
            }
11589
        </style>
11590
    <body>
11591
        <div class="error-message">
11592
            $lang_not_exportable
11593
        </div>
11594
    </body>
11595
</html>
11596
EOD;
11597
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11598
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11599
        }
11600
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11601
11602
        // Add the extra files that go along with a SCORM package.
11603
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11604
11605
        $fs = new Filesystem();
11606
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11607
11608
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11609
        $manifest = @$xmldoc->saveXML();
11610
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11611
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11612
        $zip_folder->add(
11613
            $archivePath.'/'.$temp_dir_short,
11614
            PCLZIP_OPT_REMOVE_PATH,
11615
            $archivePath.'/'.$temp_dir_short.'/'
11616
        );
11617
11618
        // Clean possible temporary files.
11619
        foreach ($files_cleanup as $file) {
11620
            $res = unlink($file);
11621
            if ($res === false) {
11622
                error_log(
11623
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11624
                    0
11625
                );
11626
            }
11627
        }
11628
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11629
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11630
    }
11631
11632
    /**
11633
     * @param int $lp_id
11634
     *
11635
     * @return bool
11636
     */
11637
    public function scorm_export_to_pdf($lp_id)
11638
    {
11639
        $lp_id = (int) $lp_id;
11640
        $files_to_export = [];
11641
11642
        $sessionId = api_get_session_id();
11643
        $course_data = api_get_course_info($this->cc);
11644
11645
        if (!empty($course_data)) {
11646
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11647
            $list = self::get_flat_ordered_items_list($lp_id);
11648
            if (!empty($list)) {
11649
                foreach ($list as $item_id) {
11650
                    $item = $this->items[$item_id];
11651
                    switch ($item->type) {
11652
                        case 'document':
11653
                            // Getting documents from a LP with chamilo documents
11654
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11655
                            // Try loading document from the base course.
11656
                            if (empty($file_data) && !empty($sessionId)) {
11657
                                $file_data = DocumentManager::get_document_data_by_id(
11658
                                    $item->path,
11659
                                    $this->cc,
11660
                                    false,
11661
                                    0
11662
                                );
11663
                            }
11664
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11665
                            if (file_exists($file_path)) {
11666
                                $files_to_export[] = [
11667
                                    'title' => $item->get_title(),
11668
                                    'path' => $file_path,
11669
                                ];
11670
                            }
11671
                            break;
11672
                        case 'asset': //commes from a scorm package generated by chamilo
11673
                        case 'sco':
11674
                            $file_path = $scorm_path.'/'.$item->path;
11675
                            if (file_exists($file_path)) {
11676
                                $files_to_export[] = [
11677
                                    'title' => $item->get_title(),
11678
                                    'path' => $file_path,
11679
                                ];
11680
                            }
11681
                            break;
11682
                        case 'dir':
11683
                            $files_to_export[] = [
11684
                                'title' => $item->get_title(),
11685
                                'path' => null,
11686
                            ];
11687
                            break;
11688
                    }
11689
                }
11690
            }
11691
11692
            $pdf = new PDF();
11693
            $result = $pdf->html_to_pdf(
11694
                $files_to_export,
11695
                $this->name,
11696
                $this->cc,
11697
                true,
11698
                true,
11699
                true,
11700
                $this->get_name()
11701
            );
11702
11703
            return $result;
11704
        }
11705
11706
        return false;
11707
    }
11708
11709
    /**
11710
     * Temp function to be moved in main_api or the best place around for this.
11711
     * Creates a file path if it doesn't exist.
11712
     *
11713
     * @param string $path
11714
     */
11715
    public function create_path($path)
11716
    {
11717
        $path_bits = explode('/', dirname($path));
11718
11719
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11720
        $path_built = IS_WINDOWS_OS ? '' : '/';
11721
        foreach ($path_bits as $bit) {
11722
            if (!empty($bit)) {
11723
                $new_path = $path_built.$bit;
11724
                if (is_dir($new_path)) {
11725
                    $path_built = $new_path.'/';
11726
                } else {
11727
                    mkdir($new_path, api_get_permissions_for_new_directories());
11728
                    $path_built = $new_path.'/';
11729
                }
11730
            }
11731
        }
11732
    }
11733
11734
    /**
11735
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11736
     *
11737
     * @return bool The results of the unlink function, or false if there was no image to start with
11738
     */
11739
    public function delete_lp_image()
11740
    {
11741
        $img = $this->get_preview_image();
11742
        if ($img != '') {
11743
            $del_file = $this->get_preview_image_path(null, 'sys');
11744
            if (isset($del_file) && file_exists($del_file)) {
11745
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11746
                if (file_exists($del_file_2)) {
11747
                    unlink($del_file_2);
11748
                }
11749
                $this->set_preview_image('');
11750
11751
                return @unlink($del_file);
11752
            }
11753
        }
11754
11755
        return false;
11756
    }
11757
11758
    /**
11759
     * Uploads an author image to the upload/learning_path/images directory.
11760
     *
11761
     * @param array    The image array, coming from the $_FILES superglobal
11762
     *
11763
     * @return bool True on success, false on error
11764
     */
11765
    public function upload_image($image_array)
11766
    {
11767
        if (!empty($image_array['name'])) {
11768
            $upload_ok = process_uploaded_file($image_array);
11769
            $has_attachment = true;
11770
        }
11771
11772
        if ($upload_ok && $has_attachment) {
11773
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11774
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11775
            $updir = $sys_course_path.$courseDir;
11776
            // Try to add an extension to the file if it hasn't one.
11777
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11778
11779
            if (filter_extension($new_file_name)) {
11780
                $file_extension = explode('.', $image_array['name']);
11781
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11782
                $filename = uniqid('');
11783
                $new_file_name = $filename.'.'.$file_extension;
11784
                $new_path = $updir.'/'.$new_file_name;
11785
11786
                // Resize the image.
11787
                $temp = new Image($image_array['tmp_name']);
11788
                $temp->resize(104);
11789
                $result = $temp->send_image($new_path);
11790
11791
                // Storing the image filename.
11792
                if ($result) {
11793
                    $this->set_preview_image($new_file_name);
11794
11795
                    //Resize to 64px to use on course homepage
11796
                    $temp->resize(64);
11797
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11798
11799
                    return true;
11800
                }
11801
            }
11802
        }
11803
11804
        return false;
11805
    }
11806
11807
    /**
11808
     * @param int    $lp_id
11809
     * @param string $status
11810
     */
11811
    public function set_autolaunch($lp_id, $status)
11812
    {
11813
        $course_id = api_get_course_int_id();
11814
        $lp_id = (int) $lp_id;
11815
        $status = (int) $status;
11816
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11817
11818
        // Setting everything to autolaunch = 0
11819
        $attributes['autolaunch'] = 0;
11820
        $where = [
11821
            'session_id = ? AND c_id = ? ' => [
11822
                api_get_session_id(),
11823
                $course_id,
11824
            ],
11825
        ];
11826
        Database::update($lp_table, $attributes, $where);
11827
        if ($status == 1) {
11828
            //Setting my lp_id to autolaunch = 1
11829
            $attributes['autolaunch'] = 1;
11830
            $where = [
11831
                'iid = ? AND session_id = ? AND c_id = ?' => [
11832
                    $lp_id,
11833
                    api_get_session_id(),
11834
                    $course_id,
11835
                ],
11836
            ];
11837
            Database::update($lp_table, $attributes, $where);
11838
        }
11839
    }
11840
11841
    /**
11842
     * Gets previous_item_id for the next element of the lp_item table.
11843
     *
11844
     * @author Isaac flores paz
11845
     *
11846
     * @return int Previous item ID
11847
     */
11848
    public function select_previous_item_id()
11849
    {
11850
        $course_id = api_get_course_int_id();
11851
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11852
11853
        // Get the max order of the items
11854
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11855
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11856
        $rs_max_order = Database::query($sql);
11857
        $row_max_order = Database::fetch_object($rs_max_order);
11858
        $max_order = $row_max_order->display_order;
11859
        // Get the previous item ID
11860
        $sql = "SELECT iid as previous FROM $table_lp_item
11861
                WHERE
11862
                    c_id = $course_id AND
11863
                    lp_id = ".$this->lp_id." AND
11864
                    display_order = '$max_order' ";
11865
        $rs_max = Database::query($sql);
11866
        $row_max = Database::fetch_object($rs_max);
11867
11868
        // Return the previous item ID
11869
        return $row_max->previous;
11870
    }
11871
11872
    /**
11873
     * Copies an LP.
11874
     */
11875
    public function copy()
11876
    {
11877
        // Course builder
11878
        $cb = new CourseBuilder();
11879
11880
        //Setting tools that will be copied
11881
        $cb->set_tools_to_build(['learnpaths']);
11882
11883
        //Setting elements that will be copied
11884
        $cb->set_tools_specific_id_list(
11885
            ['learnpaths' => [$this->lp_id]]
11886
        );
11887
11888
        $course = $cb->build();
11889
11890
        //Course restorer
11891
        $course_restorer = new CourseRestorer($course);
11892
        $course_restorer->set_add_text_in_items(true);
11893
        $course_restorer->set_tool_copy_settings(
11894
            ['learnpaths' => ['reset_dates' => true]]
11895
        );
11896
        $course_restorer->restore(
11897
            api_get_course_id(),
11898
            api_get_session_id(),
11899
            false,
11900
            false
11901
        );
11902
    }
11903
11904
    /**
11905
     * Verify document size.
11906
     *
11907
     * @param string $s
11908
     *
11909
     * @return bool
11910
     */
11911
    public static function verify_document_size($s)
11912
    {
11913
        $post_max = ini_get('post_max_size');
11914
        if (substr($post_max, -1, 1) == 'M') {
11915
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11916
        } elseif (substr($post_max, -1, 1) == 'G') {
11917
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11918
        }
11919
        $upl_max = ini_get('upload_max_filesize');
11920
        if (substr($upl_max, -1, 1) == 'M') {
11921
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11922
        } elseif (substr($upl_max, -1, 1) == 'G') {
11923
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11924
        }
11925
        $documents_total_space = DocumentManager::documents_total_space();
11926
        $course_max_space = DocumentManager::get_course_quota();
11927
        $total_size = filesize($s) + $documents_total_space;
11928
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11929
            return true;
11930
        }
11931
11932
        return false;
11933
    }
11934
11935
    /**
11936
     * Clear LP prerequisites.
11937
     */
11938
    public function clear_prerequisites()
11939
    {
11940
        $course_id = $this->get_course_int_id();
11941
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11942
        $lp_id = $this->get_id();
11943
        // Cleaning prerequisites
11944
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11945
                WHERE c_id = $course_id AND lp_id = $lp_id";
11946
        Database::query($sql);
11947
11948
        // Cleaning mastery score for exercises
11949
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11950
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11951
        Database::query($sql);
11952
    }
11953
11954
    public function set_previous_step_as_prerequisite_for_all_items()
11955
    {
11956
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11957
        $course_id = $this->get_course_int_id();
11958
        $lp_id = $this->get_id();
11959
11960
        if (!empty($this->items)) {
11961
            $previous_item_id = null;
11962
            $previous_item_max = 0;
11963
            $previous_item_type = null;
11964
            $last_item_not_dir = null;
11965
            $last_item_not_dir_type = null;
11966
            $last_item_not_dir_max = null;
11967
11968
            foreach ($this->ordered_items as $itemId) {
11969
                $item = $this->getItem($itemId);
11970
                // if there was a previous item... (otherwise jump to set it)
11971
                if (!empty($previous_item_id)) {
11972
                    $current_item_id = $item->get_id(); //save current id
11973
                    if ($item->get_type() != 'dir') {
11974
                        // Current item is not a folder, so it qualifies to get a prerequisites
11975
                        if ($last_item_not_dir_type == 'quiz') {
11976
                            // if previous is quiz, mark its max score as default score to be achieved
11977
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11978
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11979
                            Database::query($sql);
11980
                        }
11981
                        // now simply update the prerequisite to set it to the last non-chapter item
11982
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11983
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11984
                        Database::query($sql);
11985
                        // record item as 'non-chapter' reference
11986
                        $last_item_not_dir = $item->get_id();
11987
                        $last_item_not_dir_type = $item->get_type();
11988
                        $last_item_not_dir_max = $item->get_max();
11989
                    }
11990
                } else {
11991
                    if ($item->get_type() != 'dir') {
11992
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11993
                        $last_item_not_dir = $item->get_id();
11994
                        $last_item_not_dir_type = $item->get_type();
11995
                        $last_item_not_dir_max = $item->get_max();
11996
                    }
11997
                }
11998
                // Saving the item as "previous item" for the next loop
11999
                $previous_item_id = $item->get_id();
12000
                $previous_item_max = $item->get_max();
12001
                $previous_item_type = $item->get_type();
12002
            }
12003
        }
12004
    }
12005
12006
    /**
12007
     * @param array $params
12008
     *
12009
     * @throws \Doctrine\ORM\OptimisticLockException
12010
     *
12011
     * @return int
12012
     */
12013
    public static function createCategory($params)
12014
    {
12015
        $em = Database::getManager();
12016
        $item = new CLpCategory();
12017
        $item->setName($params['name']);
12018
        $item->setCId($params['c_id']);
12019
        $em->persist($item);
12020
        $em->flush();
12021
12022
        $id = $item->getId();
12023
12024
        $sessionId = api_get_session_id();
12025
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12026
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12027
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12028
            Database::query($sql);
12029
        }
12030
12031
        api_item_property_update(
12032
            api_get_course_info(),
12033
            TOOL_LEARNPATH_CATEGORY,
12034
            $id,
12035
            'visible',
12036
            api_get_user_id()
12037
        );
12038
12039
        return $item->getId();
12040
    }
12041
12042
    /**
12043
     * @param array $params
12044
     *
12045
     * @throws \Doctrine\ORM\ORMException
12046
     * @throws \Doctrine\ORM\OptimisticLockException
12047
     * @throws \Doctrine\ORM\TransactionRequiredException
12048
     */
12049
    public static function updateCategory($params)
12050
    {
12051
        $em = Database::getManager();
12052
        /** @var CLpCategory $item */
12053
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12054
        if ($item) {
12055
            $item->setName($params['name']);
12056
            $em->merge($item);
12057
            $em->flush();
12058
        }
12059
    }
12060
12061
    /**
12062
     * @param int $id
12063
     *
12064
     * @throws \Doctrine\ORM\ORMException
12065
     * @throws \Doctrine\ORM\OptimisticLockException
12066
     * @throws \Doctrine\ORM\TransactionRequiredException
12067
     */
12068
    public static function moveUpCategory($id)
12069
    {
12070
        $id = (int) $id;
12071
        $em = Database::getManager();
12072
        /** @var CLpCategory $item */
12073
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12074
        if ($item) {
12075
            $position = $item->getPosition() - 1;
12076
            $item->setPosition($position);
12077
            $em->persist($item);
12078
            $em->flush();
12079
        }
12080
    }
12081
12082
    /**
12083
     * @param int $id
12084
     *
12085
     * @throws \Doctrine\ORM\ORMException
12086
     * @throws \Doctrine\ORM\OptimisticLockException
12087
     * @throws \Doctrine\ORM\TransactionRequiredException
12088
     */
12089
    public static function moveDownCategory($id)
12090
    {
12091
        $id = (int) $id;
12092
        $em = Database::getManager();
12093
        /** @var CLpCategory $item */
12094
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12095
        if ($item) {
12096
            $position = $item->getPosition() + 1;
12097
            $item->setPosition($position);
12098
            $em->persist($item);
12099
            $em->flush();
12100
        }
12101
    }
12102
12103
    public static function getLpList($courseId)
12104
    {
12105
        $table = Database::get_course_table(TABLE_LP_MAIN);
12106
        $courseId = (int) $courseId;
12107
12108
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
12109
        $result = Database::query($sql);
12110
12111
        return Database::store_result($result, 'ASSOC');
12112
    }
12113
12114
    /**
12115
     * @param int $courseId
12116
     *
12117
     * @throws \Doctrine\ORM\Query\QueryException
12118
     *
12119
     * @return int|mixed
12120
     */
12121
    public static function getCountCategories($courseId)
12122
    {
12123
        if (empty($courseId)) {
12124
            return 0;
12125
        }
12126
        $em = Database::getManager();
12127
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12128
        $query->setParameter('id', $courseId);
12129
12130
        return $query->getSingleScalarResult();
12131
    }
12132
12133
    /**
12134
     * @param int $courseId
12135
     *
12136
     * @return CLpCategory[]
12137
     */
12138
    public static function getCategories($courseId)
12139
    {
12140
        $em = Database::getManager();
12141
12142
        // Using doctrine extensions
12143
        /** @var SortableRepository $repo */
12144
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12145
12146
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12147
    }
12148
12149
    public static function getCategorySessionId($id)
12150
    {
12151
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12152
            return 0;
12153
        }
12154
12155
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12156
        $id = (int) $id;
12157
12158
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12159
        $result = Database::query($sql);
12160
        $result = Database::fetch_array($result, 'ASSOC');
12161
12162
        if ($result) {
12163
12164
            return (int) $result['session_id'];
12165
        }
12166
12167
        return 0;
12168
    }
12169
12170
    /**
12171
     * @param int $id
12172
     *
12173
     * @throws \Doctrine\ORM\ORMException
12174
     * @throws \Doctrine\ORM\OptimisticLockException
12175
     * @throws \Doctrine\ORM\TransactionRequiredException
12176
     *
12177
     * @return CLpCategory
12178
     */
12179
    public static function getCategory($id)
12180
    {
12181
        $id = (int) $id;
12182
        $em = Database::getManager();
12183
12184
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12185
    }
12186
12187
    /**
12188
     * @param int $courseId
12189
     *
12190
     * @return array
12191
     */
12192
    public static function getCategoryByCourse($courseId)
12193
    {
12194
        $em = Database::getManager();
12195
12196
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12197
    }
12198
12199
    /**
12200
     * @param int $id
12201
     *
12202
     * @throws \Doctrine\ORM\ORMException
12203
     * @throws \Doctrine\ORM\OptimisticLockException
12204
     * @throws \Doctrine\ORM\TransactionRequiredException
12205
     *
12206
     * @return mixed
12207
     */
12208
    public static function deleteCategory($id)
12209
    {
12210
        $em = Database::getManager();
12211
        $item = self::getCategory($id);
12212
        if ($item) {
12213
            $courseId = $item->getCId();
12214
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12215
            $query->setParameter('id', $courseId);
12216
            $query->setParameter('catId', $item->getId());
12217
            $lps = $query->getResult();
12218
12219
            // Setting category = 0.
12220
            if ($lps) {
12221
                foreach ($lps as $lpItem) {
12222
                    $lpItem->setCategoryId(0);
12223
                }
12224
            }
12225
12226
            // Removing category.
12227
            $em->remove($item);
12228
            $em->flush();
12229
12230
            $courseInfo = api_get_course_info_by_id($courseId);
12231
            $sessionId = api_get_session_id();
12232
12233
            // Delete link tool
12234
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12235
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12236
            // Delete tools
12237
            $sql = "DELETE FROM $tbl_tool
12238
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12239
            Database::query($sql);
12240
12241
            return true;
12242
        }
12243
12244
        return false;
12245
    }
12246
12247
    /**
12248
     * @param int  $courseId
12249
     * @param bool $addSelectOption
12250
     *
12251
     * @return mixed
12252
     */
12253
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12254
    {
12255
        $items = self::getCategoryByCourse($courseId);
12256
        $cats = [];
12257
        if ($addSelectOption) {
12258
            $cats = [get_lang('SelectACategory')];
12259
        }
12260
12261
        if (!empty($items)) {
12262
            foreach ($items as $cat) {
12263
                $cats[$cat->getId()] = $cat->getName();
12264
            }
12265
        }
12266
12267
        return $cats;
12268
    }
12269
12270
    /**
12271
     * @param string $courseCode
12272
     * @param int    $lpId
12273
     * @param int    $user_id
12274
     *
12275
     * @return learnpath
12276
     */
12277
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12278
    {
12279
        $debug = 0;
12280
        $learnPath = null;
12281
        $lpObject = Session::read('lpobject');
12282
        if ($lpObject !== null) {
12283
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12284
            if ($debug) {
12285
                error_log('getLpFromSession: unserialize');
12286
                error_log('------getLpFromSession------');
12287
                error_log('------unserialize------');
12288
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12289
                error_log("api_get_sessionid: ".api_get_session_id());
12290
            }
12291
        }
12292
12293
        if (!is_object($learnPath)) {
12294
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12295
            if ($debug) {
12296
                error_log('------getLpFromSession------');
12297
                error_log('getLpFromSession: create new learnpath');
12298
                error_log("create new LP with $courseCode - $lpId - $user_id");
12299
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12300
                error_log("api_get_sessionid: ".api_get_session_id());
12301
            }
12302
        }
12303
12304
        return $learnPath;
12305
    }
12306
12307
    /**
12308
     * @param int $itemId
12309
     *
12310
     * @return learnpathItem|false
12311
     */
12312
    public function getItem($itemId)
12313
    {
12314
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12315
            return $this->items[$itemId];
12316
        }
12317
12318
        return false;
12319
    }
12320
12321
    /**
12322
     * @return int
12323
     */
12324
    public function getCurrentAttempt()
12325
    {
12326
        $attempt = $this->getItem($this->get_current_item_id());
12327
        if ($attempt) {
12328
            $attemptId = $attempt->get_attempt_id();
12329
12330
            return $attemptId;
12331
        }
12332
12333
        return 0;
12334
    }
12335
12336
    /**
12337
     * @return int
12338
     */
12339
    public function getCategoryId()
12340
    {
12341
        return (int) $this->categoryId;
12342
    }
12343
12344
    /**
12345
     * @param int $categoryId
12346
     *
12347
     * @return bool
12348
     */
12349
    public function setCategoryId($categoryId)
12350
    {
12351
        $this->categoryId = (int) $categoryId;
12352
        $table = Database::get_course_table(TABLE_LP_MAIN);
12353
        $lp_id = $this->get_id();
12354
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12355
                WHERE iid = $lp_id";
12356
        Database::query($sql);
12357
12358
        return true;
12359
    }
12360
12361
    /**
12362
     * Get whether this is a learning path with the possibility to subscribe
12363
     * users or not.
12364
     *
12365
     * @return int
12366
     */
12367
    public function getSubscribeUsers()
12368
    {
12369
        return $this->subscribeUsers;
12370
    }
12371
12372
    /**
12373
     * Set whether this is a learning path with the possibility to subscribe
12374
     * users or not.
12375
     *
12376
     * @param int $value (0 = false, 1 = true)
12377
     *
12378
     * @return bool
12379
     */
12380
    public function setSubscribeUsers($value)
12381
    {
12382
        $this->subscribeUsers = (int) $value;
12383
        $table = Database::get_course_table(TABLE_LP_MAIN);
12384
        $lp_id = $this->get_id();
12385
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12386
                WHERE iid = $lp_id";
12387
        Database::query($sql);
12388
12389
        return true;
12390
    }
12391
12392
    /**
12393
     * Calculate the count of stars for a user in this LP
12394
     * This calculation is based on the following rules:
12395
     * - the student gets one star when he gets to 50% of the learning path
12396
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12397
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12398
     * - the student gets the final star when the score for the *last* test is >= 80%.
12399
     *
12400
     * @param int $sessionId Optional. The session ID
12401
     *
12402
     * @return int The count of stars
12403
     */
12404
    public function getCalculateStars($sessionId = 0)
12405
    {
12406
        $stars = 0;
12407
        $progress = self::getProgress(
12408
            $this->lp_id,
12409
            $this->user_id,
12410
            $this->course_int_id,
12411
            $sessionId
12412
        );
12413
12414
        if ($progress >= 50) {
12415
            $stars++;
12416
        }
12417
12418
        // Calculate stars chapters evaluation
12419
        $exercisesItems = $this->getExercisesItems();
12420
12421
        if (!empty($exercisesItems)) {
12422
            $totalResult = 0;
12423
12424
            foreach ($exercisesItems as $exerciseItem) {
12425
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12426
                    $this->user_id,
12427
                    $exerciseItem->path,
12428
                    $this->course_int_id,
12429
                    $sessionId,
12430
                    $this->lp_id,
12431
                    $exerciseItem->db_id
12432
                );
12433
12434
                $exerciseResultInfo = end($exerciseResultInfo);
12435
12436
                if (!$exerciseResultInfo) {
12437
                    continue;
12438
                }
12439
12440
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12441
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12442
                } else {
12443
                    $exerciseResult = 0;
12444
                }
12445
                $totalResult += $exerciseResult;
12446
            }
12447
12448
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12449
12450
            if ($totalExerciseAverage >= 50) {
12451
                $stars++;
12452
            }
12453
12454
            if ($totalExerciseAverage >= 80) {
12455
                $stars++;
12456
            }
12457
        }
12458
12459
        // Calculate star for final evaluation
12460
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12461
12462
        if (!empty($finalEvaluationItem)) {
12463
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12464
                $this->user_id,
12465
                $finalEvaluationItem->path,
12466
                $this->course_int_id,
12467
                $sessionId,
12468
                $this->lp_id,
12469
                $finalEvaluationItem->db_id
12470
            );
12471
12472
            $evaluationResultInfo = end($evaluationResultInfo);
12473
12474
            if ($evaluationResultInfo) {
12475
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12476
12477
                if ($evaluationResult >= 80) {
12478
                    $stars++;
12479
                }
12480
            }
12481
        }
12482
12483
        return $stars;
12484
    }
12485
12486
    /**
12487
     * Get the items of exercise type.
12488
     *
12489
     * @return array The items. Otherwise return false
12490
     */
12491
    public function getExercisesItems()
12492
    {
12493
        $exercises = [];
12494
        foreach ($this->items as $item) {
12495
            if ($item->type != 'quiz') {
12496
                continue;
12497
            }
12498
            $exercises[] = $item;
12499
        }
12500
12501
        array_pop($exercises);
12502
12503
        return $exercises;
12504
    }
12505
12506
    /**
12507
     * Get the item of exercise type (evaluation type).
12508
     *
12509
     * @return array The final evaluation. Otherwise return false
12510
     */
12511
    public function getFinalEvaluationItem()
12512
    {
12513
        $exercises = [];
12514
        foreach ($this->items as $item) {
12515
            if ($item->type != 'quiz') {
12516
                continue;
12517
            }
12518
12519
            $exercises[] = $item;
12520
        }
12521
12522
        return array_pop($exercises);
12523
    }
12524
12525
    /**
12526
     * Calculate the total points achieved for the current user in this learning path.
12527
     *
12528
     * @param int $sessionId Optional. The session Id
12529
     *
12530
     * @return int
12531
     */
12532
    public function getCalculateScore($sessionId = 0)
12533
    {
12534
        // Calculate stars chapters evaluation
12535
        $exercisesItems = $this->getExercisesItems();
12536
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12537
        $totalExercisesResult = 0;
12538
        $totalEvaluationResult = 0;
12539
12540
        if ($exercisesItems !== false) {
12541
            foreach ($exercisesItems as $exerciseItem) {
12542
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12543
                    $this->user_id,
12544
                    $exerciseItem->path,
12545
                    $this->course_int_id,
12546
                    $sessionId,
12547
                    $this->lp_id,
12548
                    $exerciseItem->db_id
12549
                );
12550
12551
                $exerciseResultInfo = end($exerciseResultInfo);
12552
12553
                if (!$exerciseResultInfo) {
12554
                    continue;
12555
                }
12556
12557
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12558
            }
12559
        }
12560
12561
        if (!empty($finalEvaluationItem)) {
12562
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12563
                $this->user_id,
12564
                $finalEvaluationItem->path,
12565
                $this->course_int_id,
12566
                $sessionId,
12567
                $this->lp_id,
12568
                $finalEvaluationItem->db_id
12569
            );
12570
12571
            $evaluationResultInfo = end($evaluationResultInfo);
12572
12573
            if ($evaluationResultInfo) {
12574
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12575
            }
12576
        }
12577
12578
        return $totalExercisesResult + $totalEvaluationResult;
12579
    }
12580
12581
    /**
12582
     * Check if URL is not allowed to be show in a iframe.
12583
     *
12584
     * @param string $src
12585
     *
12586
     * @return string
12587
     */
12588
    public function fixBlockedLinks($src)
12589
    {
12590
        $urlInfo = parse_url($src);
12591
12592
        $platformProtocol = 'https';
12593
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12594
            $platformProtocol = 'http';
12595
        }
12596
12597
        $protocolFixApplied = false;
12598
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12599
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12600
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12601
12602
        if ($platformProtocol != $scheme) {
12603
            Session::write('x_frame_source', $src);
12604
            $src = 'blank.php?error=x_frames_options';
12605
            $protocolFixApplied = true;
12606
        }
12607
12608
        if ($protocolFixApplied == false) {
12609
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12610
                // Check X-Frame-Options
12611
                $ch = curl_init();
12612
                $options = [
12613
                    CURLOPT_URL => $src,
12614
                    CURLOPT_RETURNTRANSFER => true,
12615
                    CURLOPT_HEADER => true,
12616
                    CURLOPT_FOLLOWLOCATION => true,
12617
                    CURLOPT_ENCODING => "",
12618
                    CURLOPT_AUTOREFERER => true,
12619
                    CURLOPT_CONNECTTIMEOUT => 120,
12620
                    CURLOPT_TIMEOUT => 120,
12621
                    CURLOPT_MAXREDIRS => 10,
12622
                ];
12623
12624
                $proxySettings = api_get_configuration_value('proxy_settings');
12625
                if (!empty($proxySettings) &&
12626
                    isset($proxySettings['curl_setopt_array'])
12627
                ) {
12628
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12629
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12630
                }
12631
12632
                curl_setopt_array($ch, $options);
12633
                $response = curl_exec($ch);
12634
                $httpCode = curl_getinfo($ch);
12635
                $headers = substr($response, 0, $httpCode['header_size']);
12636
12637
                $error = false;
12638
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12639
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12640
                ) {
12641
                    $error = true;
12642
                }
12643
12644
                if ($error) {
12645
                    Session::write('x_frame_source', $src);
12646
                    $src = 'blank.php?error=x_frames_options';
12647
                }
12648
            }
12649
        }
12650
12651
        return $src;
12652
    }
12653
12654
    /**
12655
     * Check if this LP has a created forum in the basis course.
12656
     *
12657
     * @return bool
12658
     */
12659
    public function lpHasForum()
12660
    {
12661
        $forumTable = Database::get_course_table(TABLE_FORUM);
12662
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12663
12664
        $fakeFrom = "
12665
            $forumTable f
12666
            INNER JOIN $itemProperty ip
12667
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12668
        ";
12669
12670
        $resultData = Database::select(
12671
            'COUNT(f.iid) AS qty',
12672
            $fakeFrom,
12673
            [
12674
                'where' => [
12675
                    'ip.visibility != ? AND ' => 2,
12676
                    'ip.tool = ? AND ' => TOOL_FORUM,
12677
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12678
                    'f.lp_id = ?' => intval($this->lp_id),
12679
                ],
12680
            ],
12681
            'first'
12682
        );
12683
12684
        return $resultData['qty'] > 0;
12685
    }
12686
12687
    /**
12688
     * Get the forum for this learning path.
12689
     *
12690
     * @param int $sessionId
12691
     *
12692
     * @return bool
12693
     */
12694
    public function getForum($sessionId = 0)
12695
    {
12696
        $forumTable = Database::get_course_table(TABLE_FORUM);
12697
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12698
12699
        $fakeFrom = "$forumTable f
12700
            INNER JOIN $itemProperty ip ";
12701
12702
        if ($this->lp_session_id == 0) {
12703
            $fakeFrom .= "
12704
                ON (
12705
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12706
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12707
                    )
12708
                )
12709
            ";
12710
        } else {
12711
            $fakeFrom .= "
12712
                ON (
12713
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12714
                )
12715
            ";
12716
        }
12717
12718
        $resultData = Database::select(
12719
            'f.*',
12720
            $fakeFrom,
12721
            [
12722
                'where' => [
12723
                    'ip.visibility != ? AND ' => 2,
12724
                    'ip.tool = ? AND ' => TOOL_FORUM,
12725
                    'f.session_id = ? AND ' => $sessionId,
12726
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12727
                    'f.lp_id = ?' => intval($this->lp_id),
12728
                ],
12729
            ],
12730
            'first'
12731
        );
12732
12733
        if (empty($resultData)) {
12734
            return false;
12735
        }
12736
12737
        return $resultData;
12738
    }
12739
12740
    /**
12741
     * Create a forum for this learning path.
12742
     *
12743
     * @param int $forumCategoryId
12744
     *
12745
     * @return int The forum ID if was created. Otherwise return false
12746
     */
12747
    public function createForum($forumCategoryId)
12748
    {
12749
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12750
12751
        $forumId = store_forum(
12752
            [
12753
                'lp_id' => $this->lp_id,
12754
                'forum_title' => $this->name,
12755
                'forum_comment' => null,
12756
                'forum_category' => (int) $forumCategoryId,
12757
                'students_can_edit_group' => ['students_can_edit' => 0],
12758
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12759
                'default_view_type_group' => ['default_view_type' => 'flat'],
12760
                'group_forum' => 0,
12761
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12762
            ],
12763
            [],
12764
            true
12765
        );
12766
12767
        return $forumId;
12768
    }
12769
12770
    /**
12771
     * Get the LP Final Item form.
12772
     *
12773
     * @throws Exception
12774
     * @throws HTML_QuickForm_Error
12775
     *
12776
     * @return string
12777
     */
12778
    public function getFinalItemForm()
12779
    {
12780
        $finalItem = $this->getFinalItem();
12781
        $title = '';
12782
12783
        if ($finalItem) {
12784
            $title = $finalItem->get_title();
12785
            $buttonText = get_lang('Save');
12786
            $content = $this->getSavedFinalItem();
12787
        } else {
12788
            $buttonText = get_lang('LPCreateDocument');
12789
            $content = $this->getFinalItemTemplate();
12790
        }
12791
12792
        $courseInfo = api_get_course_info();
12793
        $result = $this->generate_lp_folder($courseInfo);
12794
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12795
        $relative_prefix = '../../';
12796
12797
        $editorConfig = [
12798
            'ToolbarSet' => 'LearningPathDocuments',
12799
            'Width' => '100%',
12800
            'Height' => '500',
12801
            'FullPage' => true,
12802
            'CreateDocumentDir' => $relative_prefix,
12803
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12804
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12805
        ];
12806
12807
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12808
            'type' => 'document',
12809
            'lp_id' => $this->lp_id,
12810
        ]);
12811
12812
        $form = new FormValidator('final_item', 'POST', $url);
12813
        $form->addText('title', get_lang('Title'));
12814
        $form->addButtonSave($buttonText);
12815
        $form->addHtml(
12816
            Display::return_message(
12817
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12818
                'normal',
12819
                false
12820
            )
12821
        );
12822
12823
        $renderer = $form->defaultRenderer();
12824
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12825
12826
        $form->addHtmlEditor(
12827
            'content_lp_certificate',
12828
            null,
12829
            true,
12830
            false,
12831
            $editorConfig,
12832
            true
12833
        );
12834
        $form->addHidden('action', 'add_final_item');
12835
        $form->addHidden('path', Session::read('pathItem'));
12836
        $form->addHidden('previous', $this->get_last());
12837
        $form->setDefaults(
12838
            ['title' => $title, 'content_lp_certificate' => $content]
12839
        );
12840
12841
        if ($form->validate()) {
12842
            $values = $form->exportValues();
12843
            $lastItemId = $this->getLastInFirstLevel();
12844
12845
            if (!$finalItem) {
12846
                $documentId = $this->create_document(
12847
                    $this->course_info,
12848
                    $values['content_lp_certificate'],
12849
                    $values['title']
12850
                );
12851
                $this->add_item(
12852
                    0,
12853
                    $lastItemId,
12854
                    'final_item',
12855
                    $documentId,
12856
                    $values['title'],
12857
                    ''
12858
                );
12859
12860
                Display::addFlash(
12861
                    Display::return_message(get_lang('Added'))
12862
                );
12863
            } else {
12864
                $this->edit_document($this->course_info);
12865
            }
12866
        }
12867
12868
        return $form->returnForm();
12869
    }
12870
12871
    /**
12872
     * Check if the current lp item is first, both, last or none from lp list.
12873
     *
12874
     * @param int $currentItemId
12875
     *
12876
     * @return string
12877
     */
12878
    public function isFirstOrLastItem($currentItemId)
12879
    {
12880
        $lpItemId = [];
12881
        $typeListNotToVerify = self::getChapterTypes();
12882
12883
        // Using get_toc() function instead $this->items because returns the correct order of the items
12884
        foreach ($this->get_toc() as $item) {
12885
            if (!in_array($item['type'], $typeListNotToVerify)) {
12886
                $lpItemId[] = $item['id'];
12887
            }
12888
        }
12889
12890
        $lastLpItemIndex = count($lpItemId) - 1;
12891
        $position = array_search($currentItemId, $lpItemId);
12892
12893
        switch ($position) {
12894
            case 0:
12895
                if (!$lastLpItemIndex) {
12896
                    $answer = 'both';
12897
                    break;
12898
                }
12899
12900
                $answer = 'first';
12901
                break;
12902
            case $lastLpItemIndex:
12903
                $answer = 'last';
12904
                break;
12905
            default:
12906
                $answer = 'none';
12907
        }
12908
12909
        return $answer;
12910
    }
12911
12912
    /**
12913
     * Get whether this is a learning path with the accumulated SCORM time or not.
12914
     *
12915
     * @return int
12916
     */
12917
    public function getAccumulateScormTime()
12918
    {
12919
        return $this->accumulateScormTime;
12920
    }
12921
12922
    /**
12923
     * Set whether this is a learning path with the accumulated SCORM time or not.
12924
     *
12925
     * @param int $value (0 = false, 1 = true)
12926
     *
12927
     * @return bool Always returns true
12928
     */
12929
    public function setAccumulateScormTime($value)
12930
    {
12931
        $this->accumulateScormTime = (int) $value;
12932
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12933
        $lp_id = $this->get_id();
12934
        $sql = "UPDATE $lp_table
12935
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12936
                WHERE iid = $lp_id";
12937
        Database::query($sql);
12938
12939
        return true;
12940
    }
12941
12942
    /**
12943
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12944
     * the new learning path tool.
12945
     *
12946
     * The function is a big switch on tool type.
12947
     * In each case, we query the corresponding table for information and build the link
12948
     * with that information.
12949
     *
12950
     * @author Yannick Warnier <[email protected]> - rebranding based on
12951
     * previous work (display_addedresource_link_in_learnpath())
12952
     *
12953
     * @param int $course_id      Course code
12954
     * @param int $learningPathId The learning path ID (in lp table)
12955
     * @param int $id_in_path     the unique index in the items table
12956
     * @param int $lpViewId
12957
     *
12958
     * @return string
12959
     */
12960
    public static function rl_get_resource_link_for_learnpath(
12961
        $course_id,
12962
        $learningPathId,
12963
        $id_in_path,
12964
        $lpViewId
12965
    ) {
12966
        $session_id = api_get_session_id();
12967
        $course_info = api_get_course_info_by_id($course_id);
12968
12969
        $learningPathId = (int) $learningPathId;
12970
        $id_in_path = (int) $id_in_path;
12971
        $lpViewId = (int) $lpViewId;
12972
12973
        $em = Database::getManager();
12974
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12975
12976
        /** @var CLpItem $rowItem */
12977
        $rowItem = $lpItemRepo->findOneBy([
12978
            'cId' => $course_id,
12979
            'lpId' => $learningPathId,
12980
            'iid' => $id_in_path,
12981
        ]);
12982
12983
        if (!$rowItem) {
12984
            // Try one more time with "id"
12985
            /** @var CLpItem $rowItem */
12986
            $rowItem = $lpItemRepo->findOneBy([
12987
                'cId' => $course_id,
12988
                'lpId' => $learningPathId,
12989
                'id' => $id_in_path,
12990
            ]);
12991
12992
            if (!$rowItem) {
12993
                return -1;
12994
            }
12995
        }
12996
12997
        $course_code = $course_info['code'];
12998
        $type = $rowItem->getItemType();
12999
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13000
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13001
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13002
        $link = '';
13003
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13004
13005
        switch ($type) {
13006
            case 'dir':
13007
                return $main_dir_path.'lp/blank.php';
13008
            case TOOL_CALENDAR_EVENT:
13009
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13010
            case TOOL_ANNOUNCEMENT:
13011
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13012
            case TOOL_LINK:
13013
                $linkInfo = Link::getLinkInfo($id);
13014
                if (isset($linkInfo['url'])) {
13015
                    return $linkInfo['url'];
13016
                }
13017
13018
                return '';
13019
            case TOOL_QUIZ:
13020
                if (empty($id)) {
13021
                    return '';
13022
                }
13023
13024
                // Get the lp_item_view with the highest view_count.
13025
                $learnpathItemViewResult = $em
13026
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13027
                    ->findBy(
13028
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13029
                        ['viewCount' => 'DESC'],
13030
                        1
13031
                    );
13032
                /** @var CLpItemView $learnpathItemViewData */
13033
                $learnpathItemViewData = current($learnpathItemViewResult);
13034
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13035
13036
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13037
                    .http_build_query([
13038
                        'lp_init' => 1,
13039
                        'learnpath_item_view_id' => $learnpathItemViewId,
13040
                        'learnpath_id' => $learningPathId,
13041
                        'learnpath_item_id' => $id_in_path,
13042
                        'exerciseId' => $id,
13043
                    ]);
13044
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13045
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13046
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13047
                $myrow = Database::fetch_array($result);
13048
                $path = $myrow['path'];
13049
13050
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13051
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13052
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13053
            case TOOL_FORUM:
13054
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13055
            case TOOL_THREAD:
13056
                // forum post
13057
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13058
                if (empty($id)) {
13059
                    return '';
13060
                }
13061
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13062
                $result = Database::query($sql);
13063
                $myrow = Database::fetch_array($result);
13064
13065
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13066
                    .$extraParams;
13067
            case TOOL_POST:
13068
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13069
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13070
                $myrow = Database::fetch_array($result);
13071
13072
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13073
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13074
            case TOOL_READOUT_TEXT:
13075
                return api_get_path(WEB_CODE_PATH).
13076
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13077
            case TOOL_DOCUMENT:
13078
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13079
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13080
13081
                if (empty($document)) {
13082
                    // Try with normal id
13083
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13084
13085
                    if (empty($document)) {
13086
                        return '';
13087
                    }
13088
                }
13089
13090
                $documentPathInfo = pathinfo($document->getPath());
13091
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13092
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13093
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13094
13095
                $openmethod = 2;
13096
                $officedoc = false;
13097
                Session::write('openmethod', $openmethod);
13098
                Session::write('officedoc', $officedoc);
13099
13100
                if ($showDirectUrl) {
13101
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13102
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13103
                        if (Link::isPdfLink($file)) {
13104
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13105
13106
                            return $pdfUrl;
13107
                        }
13108
                    }
13109
13110
                    return $file;
13111
                }
13112
13113
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13114
            case TOOL_LP_FINAL_ITEM:
13115
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13116
                    .$extraParams;
13117
            case 'assignments':
13118
                return $main_dir_path.'work/work.php?'.$extraParams;
13119
            case TOOL_DROPBOX:
13120
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13121
            case 'introduction_text': //DEPRECATED
13122
                return '';
13123
            case TOOL_COURSE_DESCRIPTION:
13124
                return $main_dir_path.'course_description?'.$extraParams;
13125
            case TOOL_GROUP:
13126
                return $main_dir_path.'group/group.php?'.$extraParams;
13127
            case TOOL_USER:
13128
                return $main_dir_path.'user/user.php?'.$extraParams;
13129
            case TOOL_STUDENTPUBLICATION:
13130
                if (!empty($rowItem->getPath())) {
13131
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13132
                }
13133
13134
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13135
        }
13136
13137
        return $link;
13138
    }
13139
13140
    /**
13141
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13142
     *
13143
     * @author Yannick Warnier <[email protected]>
13144
     *
13145
     * @param string $course_code    Course code
13146
     * @param int    $learningPathId
13147
     * @param int    $id_in_path     The resource ID
13148
     *
13149
     * @return string
13150
     */
13151
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13152
    {
13153
        $_course = api_get_course_info($course_code);
13154
        if (empty($_course)) {
13155
            return '';
13156
        }
13157
        $course_id = $_course['real_id'];
13158
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13159
        $learningPathId = (int) $learningPathId;
13160
        $id_in_path = (int) $id_in_path;
13161
13162
        $sql = "SELECT item_type, title, ref
13163
                FROM $tbl_lp_item
13164
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13165
        $res_item = Database::query($sql);
13166
13167
        if (Database::num_rows($res_item) < 1) {
13168
            return '';
13169
        }
13170
        $row_item = Database::fetch_array($res_item);
13171
        $type = strtolower($row_item['item_type']);
13172
        $id = $row_item['ref'];
13173
        $output = '';
13174
13175
        switch ($type) {
13176
            case TOOL_CALENDAR_EVENT:
13177
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13178
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13179
                $myrow = Database::fetch_array($result);
13180
                $output = $myrow['title'];
13181
                break;
13182
            case TOOL_ANNOUNCEMENT:
13183
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13184
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13185
                $myrow = Database::fetch_array($result);
13186
                $output = $myrow['title'];
13187
                break;
13188
            case TOOL_LINK:
13189
                // Doesn't take $target into account.
13190
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13191
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13192
                $myrow = Database::fetch_array($result);
13193
                $output = $myrow['title'];
13194
                break;
13195
            case TOOL_QUIZ:
13196
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13197
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13198
                $myrow = Database::fetch_array($result);
13199
                $output = $myrow['title'];
13200
                break;
13201
            case TOOL_FORUM:
13202
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13203
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13204
                $myrow = Database::fetch_array($result);
13205
                $output = $myrow['forum_name'];
13206
                break;
13207
            case TOOL_THREAD:
13208
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13209
                // Grabbing the title of the post.
13210
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13211
                $result_title = Database::query($sql_title);
13212
                $myrow_title = Database::fetch_array($result_title);
13213
                $output = $myrow_title['post_title'];
13214
                break;
13215
            case TOOL_POST:
13216
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13217
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13218
                $result = Database::query($sql);
13219
                $post = Database::fetch_array($result);
13220
                $output = $post['post_title'];
13221
                break;
13222
            case 'dir':
13223
            case TOOL_DOCUMENT:
13224
                $title = $row_item['title'];
13225
                $output = '-';
13226
                if (!empty($title)) {
13227
                    $output = $title;
13228
                }
13229
                break;
13230
            case 'hotpotatoes':
13231
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13232
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13233
                $myrow = Database::fetch_array($result);
13234
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13235
                $last = count($pathname) - 1; // Making a correct name for the link.
13236
                $filename = $pathname[$last]; // Making a correct name for the link.
13237
                $myrow['path'] = rawurlencode($myrow['path']);
13238
                $output = $filename;
13239
                break;
13240
        }
13241
13242
        return stripslashes($output);
13243
    }
13244
13245
    /**
13246
     * Get the parent names for the current item.
13247
     *
13248
     * @param int $newItemId Optional. The item ID
13249
     *
13250
     * @return array
13251
     */
13252
    public function getCurrentItemParentNames($newItemId = 0)
13253
    {
13254
        $newItemId = $newItemId ?: $this->get_current_item_id();
13255
        $return = [];
13256
        $item = $this->getItem($newItemId);
13257
        $parent = $this->getItem($item->get_parent());
13258
13259
        while ($parent) {
13260
            $return[] = $parent->get_title();
13261
            $parent = $this->getItem($parent->get_parent());
13262
        }
13263
13264
        return array_reverse($return);
13265
    }
13266
13267
    /**
13268
     * Reads and process "lp_subscription_settings" setting.
13269
     *
13270
     * @return array
13271
     */
13272
    public static function getSubscriptionSettings()
13273
    {
13274
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13275
        if (empty($subscriptionSettings)) {
13276
            // By default allow both settings
13277
            $subscriptionSettings = [
13278
                'allow_add_users_to_lp' => true,
13279
                'allow_add_users_to_lp_category' => true,
13280
            ];
13281
        } else {
13282
            $subscriptionSettings = $subscriptionSettings['options'];
13283
        }
13284
13285
        return $subscriptionSettings;
13286
    }
13287
13288
    /**
13289
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13290
     */
13291
    public function exportToCourseBuildFormat()
13292
    {
13293
        if (!api_is_allowed_to_edit()) {
13294
            return false;
13295
        }
13296
13297
        $courseBuilder = new CourseBuilder();
13298
        $itemList = [];
13299
        /** @var learnpathItem $item */
13300
        foreach ($this->items as $item) {
13301
            $itemList[$item->get_type()][] = $item->get_path();
13302
        }
13303
13304
        if (empty($itemList)) {
13305
            return false;
13306
        }
13307
13308
        if (isset($itemList['document'])) {
13309
            // Get parents
13310
            foreach ($itemList['document'] as $documentId) {
13311
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13312
                if (!empty($documentInfo['parents'])) {
13313
                    foreach ($documentInfo['parents'] as $parentInfo) {
13314
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13315
                            continue;
13316
                        }
13317
                        $itemList['document'][] = $parentInfo['iid'];
13318
                    }
13319
                }
13320
            }
13321
13322
            $courseInfo = api_get_course_info();
13323
            foreach ($itemList['document'] as $documentId) {
13324
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13325
                $items = DocumentManager::get_resources_from_source_html(
13326
                    $documentInfo['absolute_path'],
13327
                    true,
13328
                    TOOL_DOCUMENT
13329
                );
13330
13331
                if (!empty($items)) {
13332
                    foreach ($items as $item) {
13333
                        // Get information about source url
13334
                        $url = $item[0]; // url
13335
                        $scope = $item[1]; // scope (local, remote)
13336
                        $type = $item[2]; // type (rel, abs, url)
13337
13338
                        $origParseUrl = parse_url($url);
13339
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13340
13341
                        if ($scope == 'local') {
13342
                            if ($type == 'abs' || $type == 'rel') {
13343
                                $documentFile = strstr($realOrigPath, 'document');
13344
                                if (strpos($realOrigPath, $documentFile) !== false) {
13345
                                    $documentFile = str_replace('document', '', $documentFile);
13346
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13347
                                    // Document found! Add it to the list
13348
                                    if ($itemDocumentId) {
13349
                                        $itemList['document'][] = $itemDocumentId;
13350
                                    }
13351
                                }
13352
                            }
13353
                        }
13354
                    }
13355
                }
13356
            }
13357
13358
            $courseBuilder->build_documents(
13359
                api_get_session_id(),
13360
                $this->get_course_int_id(),
13361
                true,
13362
                $itemList['document']
13363
            );
13364
        }
13365
13366
        if (isset($itemList['quiz'])) {
13367
            $courseBuilder->build_quizzes(
13368
                api_get_session_id(),
13369
                $this->get_course_int_id(),
13370
                true,
13371
                $itemList['quiz']
13372
            );
13373
        }
13374
13375
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13376
13377
        /*if (!empty($itemList['thread'])) {
13378
            $postList = [];
13379
            foreach ($itemList['thread'] as $postId) {
13380
                $post = get_post_information($postId);
13381
                if ($post) {
13382
                    if (!isset($itemList['forum'])) {
13383
                        $itemList['forum'] = [];
13384
                    }
13385
                    $itemList['forum'][] = $post['forum_id'];
13386
                    $postList[] = $postId;
13387
                }
13388
            }
13389
13390
            if (!empty($postList)) {
13391
                $courseBuilder->build_forum_posts(
13392
                    $this->get_course_int_id(),
13393
                    null,
13394
                    null,
13395
                    $postList
13396
                );
13397
            }
13398
        }*/
13399
13400
        if (!empty($itemList['thread'])) {
13401
            $threadList = [];
13402
            $em = Database::getManager();
13403
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13404
            foreach ($itemList['thread'] as $threadId) {
13405
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13406
                $thread = $repo->find($threadId);
13407
                if ($thread) {
13408
                    $itemList['forum'][] = $thread->getForumId();
13409
                    $threadList[] = $thread->getIid();
13410
                }
13411
            }
13412
13413
            if (!empty($threadList)) {
13414
                $courseBuilder->build_forum_topics(
13415
                    api_get_session_id(),
13416
                    $this->get_course_int_id(),
13417
                    null,
13418
                    $threadList
13419
                );
13420
            }
13421
        }
13422
13423
        $forumCategoryList = [];
13424
        if (isset($itemList['forum'])) {
13425
            foreach ($itemList['forum'] as $forumId) {
13426
                $forumInfo = get_forums($forumId);
13427
                $forumCategoryList[] = $forumInfo['forum_category'];
13428
            }
13429
        }
13430
13431
        if (!empty($forumCategoryList)) {
13432
            $courseBuilder->build_forum_category(
13433
                api_get_session_id(),
13434
                $this->get_course_int_id(),
13435
                true,
13436
                $forumCategoryList
13437
            );
13438
        }
13439
13440
        if (!empty($itemList['forum'])) {
13441
            $courseBuilder->build_forums(
13442
                api_get_session_id(),
13443
                $this->get_course_int_id(),
13444
                true,
13445
                $itemList['forum']
13446
            );
13447
        }
13448
13449
        if (isset($itemList['link'])) {
13450
            $courseBuilder->build_links(
13451
                api_get_session_id(),
13452
                $this->get_course_int_id(),
13453
                true,
13454
                $itemList['link']
13455
            );
13456
        }
13457
13458
        if (!empty($itemList['student_publication'])) {
13459
            $courseBuilder->build_works(
13460
                api_get_session_id(),
13461
                $this->get_course_int_id(),
13462
                true,
13463
                $itemList['student_publication']
13464
            );
13465
        }
13466
13467
        $courseBuilder->build_learnpaths(
13468
            api_get_session_id(),
13469
            $this->get_course_int_id(),
13470
            true,
13471
            [$this->get_id()],
13472
            false
13473
        );
13474
13475
        $courseBuilder->restoreDocumentsFromList();
13476
13477
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13478
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13479
        $result = DocumentManager::file_send_for_download(
13480
            $zipPath,
13481
            true,
13482
            $this->get_name().'.zip'
13483
        );
13484
13485
        if ($result) {
13486
            api_not_allowed();
13487
        }
13488
13489
        return true;
13490
    }
13491
13492
    /**
13493
     * Get whether this is a learning path with the accumulated work time or not.
13494
     *
13495
     * @return int
13496
     */
13497
    public function getAccumulateWorkTime()
13498
    {
13499
        return (int) $this->accumulateWorkTime;
13500
    }
13501
13502
    /**
13503
     * Get whether this is a learning path with the accumulated work time or not.
13504
     *
13505
     * @return int
13506
     */
13507
    public function getAccumulateWorkTimeTotalCourse()
13508
    {
13509
        $table = Database::get_course_table(TABLE_LP_MAIN);
13510
        $sql = "SELECT SUM(accumulate_work_time) AS total
13511
                FROM $table
13512
                WHERE c_id = ".$this->course_int_id;
13513
        $result = Database::query($sql);
13514
        $row = Database::fetch_array($result);
13515
13516
        return (int) $row['total'];
13517
    }
13518
13519
    /**
13520
     * Set whether this is a learning path with the accumulated work time or not.
13521
     *
13522
     * @param int $value (0 = false, 1 = true)
13523
     *
13524
     * @return bool
13525
     */
13526
    public function setAccumulateWorkTime($value)
13527
    {
13528
        if (!api_get_configuration_value('lp_minimum_time')) {
13529
            return false;
13530
        }
13531
13532
        $this->accumulateWorkTime = (int) $value;
13533
        $table = Database::get_course_table(TABLE_LP_MAIN);
13534
        $lp_id = $this->get_id();
13535
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13536
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13537
        Database::query($sql);
13538
13539
        return true;
13540
    }
13541
13542
    /**
13543
     * @param int $lpId
13544
     * @param int $courseId
13545
     *
13546
     * @return mixed
13547
     */
13548
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13549
    {
13550
        $lpId = (int) $lpId;
13551
        $courseId = (int) $courseId;
13552
13553
        $table = Database::get_course_table(TABLE_LP_MAIN);
13554
        $sql = "SELECT accumulate_work_time
13555
                FROM $table
13556
                WHERE c_id = $courseId AND id = $lpId";
13557
        $result = Database::query($sql);
13558
        $row = Database::fetch_array($result);
13559
13560
        return $row['accumulate_work_time'];
13561
    }
13562
13563
    /**
13564
     * @param int $courseId
13565
     *
13566
     * @return int
13567
     */
13568
    public static function getAccumulateWorkTimeTotal($courseId)
13569
    {
13570
        $table = Database::get_course_table(TABLE_LP_MAIN);
13571
        $courseId = (int) $courseId;
13572
        $sql = "SELECT SUM(accumulate_work_time) AS total
13573
                FROM $table
13574
                WHERE c_id = $courseId";
13575
        $result = Database::query($sql);
13576
        $row = Database::fetch_array($result);
13577
13578
        return (int) $row['total'];
13579
    }
13580
13581
    /**
13582
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13583
     * and put the images in.
13584
     *
13585
     * @return array
13586
     */
13587
    public static function getIconSelect()
13588
    {
13589
        $theme = api_get_visual_theme();
13590
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13591
        $icons = ['' => get_lang('SelectAnOption')];
13592
13593
        if (is_dir($path)) {
13594
            $finder = new Finder();
13595
            $finder->files()->in($path);
13596
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13597
            /** @var SplFileInfo $file */
13598
            foreach ($finder as $file) {
13599
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13600
                    $icons[$file->getFilename()] = $file->getFilename();
13601
                }
13602
            }
13603
        }
13604
13605
        return $icons;
13606
    }
13607
13608
    /**
13609
     * @param int $lpId
13610
     *
13611
     * @return string
13612
     */
13613
    public static function getSelectedIcon($lpId)
13614
    {
13615
        $extraFieldValue = new ExtraFieldValue('lp');
13616
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13617
        $icon = '';
13618
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13619
            $icon = $lpIcon['value'];
13620
        }
13621
13622
        return $icon;
13623
    }
13624
13625
    /**
13626
     * @param int $lpId
13627
     *
13628
     * @return string
13629
     */
13630
    public static function getSelectedIconHtml($lpId)
13631
    {
13632
        $icon = self::getSelectedIcon($lpId);
13633
13634
        if (empty($icon)) {
13635
            return '';
13636
        }
13637
13638
        $theme = api_get_visual_theme();
13639
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13640
13641
        return Display::img($path);
13642
    }
13643
13644
    /**
13645
     * @param string $value
13646
     *
13647
     * @return string
13648
     */
13649
    public function cleanItemTitle($value)
13650
    {
13651
        $value = Security::remove_XSS(strip_tags($value));
13652
13653
        return $value;
13654
    }
13655
13656
    public function setItemTitle(FormValidator $form)
13657
    {
13658
        if (api_get_configuration_value('save_titles_as_html')) {
13659
            $form->addHtmlEditor(
13660
                'title',
13661
                get_lang('Title'),
13662
                true,
13663
                false,
13664
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13665
            );
13666
        } else {
13667
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13668
            $form->applyFilter('title', 'trim');
13669
            $form->applyFilter('title', 'html_filter');
13670
        }
13671
    }
13672
13673
    /**
13674
     * @return array
13675
     */
13676
    public function getItemsForForm($addParentCondition = false)
13677
    {
13678
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13679
        $course_id = api_get_course_int_id();
13680
13681
        $sql = "SELECT * FROM $tbl_lp_item
13682
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13683
13684
        if ($addParentCondition) {
13685
            $sql .= ' AND parent_item_id = 0 ';
13686
        }
13687
        $sql .= ' ORDER BY display_order ASC';
13688
13689
        $result = Database::query($sql);
13690
        $arrLP = [];
13691
        while ($row = Database::fetch_array($result)) {
13692
            $arrLP[] = [
13693
                'iid' => $row['iid'],
13694
                'id' => $row['iid'],
13695
                'item_type' => $row['item_type'],
13696
                'title' => $this->cleanItemTitle($row['title']),
13697
                'title_raw' => $row['title'],
13698
                'path' => $row['path'],
13699
                'description' => Security::remove_XSS($row['description']),
13700
                'parent_item_id' => $row['parent_item_id'],
13701
                'previous_item_id' => $row['previous_item_id'],
13702
                'next_item_id' => $row['next_item_id'],
13703
                'display_order' => $row['display_order'],
13704
                'max_score' => $row['max_score'],
13705
                'min_score' => $row['min_score'],
13706
                'mastery_score' => $row['mastery_score'],
13707
                'prerequisite' => $row['prerequisite'],
13708
                'max_time_allowed' => $row['max_time_allowed'],
13709
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13710
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13711
            ];
13712
        }
13713
13714
        return $arrLP;
13715
    }
13716
13717
    /**
13718
     * Gets whether this SCORM learning path has been marked to use the score
13719
     * as progress. Takes into account whether the learnpath matches (SCORM
13720
     * content + less than 2 items).
13721
     *
13722
     * @return bool True if the score should be used as progress, false otherwise
13723
     */
13724
    public function getUseScoreAsProgress()
13725
    {
13726
        // If not a SCORM, we don't care about the setting
13727
        if ($this->get_type() != 2) {
13728
            return false;
13729
        }
13730
        // If more than one step in the SCORM, we don't care about the setting
13731
        if ($this->get_total_items_count() > 1) {
13732
            return false;
13733
        }
13734
        $extraFieldValue = new ExtraFieldValue('lp');
13735
        $doUseScore = false;
13736
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
13737
        if (!empty($useScore) && isset($useScore['value'])) {
13738
            $doUseScore = $useScore['value'];
13739
        }
13740
13741
        return $doUseScore;
13742
    }
13743
13744
    /**
13745
     * Get the user identifier (user_id or username
13746
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
13747
     *
13748
     * @return string User ID or username, depending on configuration setting
13749
     */
13750
    public static function getUserIdentifierForExternalServices()
13751
    {
13752
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
13753
            return api_get_user_info(api_get_user_id())['username'];
13754
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
13755
            $extraFieldValue = new ExtraFieldValue('user');
13756
            $extrafield = $extraFieldValue->get_values_by_handler_and_field_variable(api_get_user_id(), api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id'));
13757
13758
            return $extrafield['value'];
13759
        } else {
13760
            return api_get_user_id();
13761
        }
13762
    }
13763
13764
    /**
13765
     * Save the new order for learning path items.
13766
     *
13767
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
13768
     *
13769
     * @param array $orderList A associative array with item ID as key and parent ID as value.
13770
     * @param int   $courseId
13771
     */
13772
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
13773
    {
13774
        $courseId = $courseId ?: api_get_course_int_id();
13775
        $itemList = new LpItemOrderList();
13776
13777
        foreach ($orderList as $id => $parentId) {
13778
            $item = new LpOrderItem($id, $parentId);
13779
            $itemList->add($item);
13780
        }
13781
13782
        $parents = $itemList->getListOfParents();
13783
13784
        foreach ($parents as $parentId) {
13785
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
13786
            $previous_item_id = 0;
13787
            for ($i = 0; $i < count($sameParentLpItemList->list); $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...
13788
                $item_id = $sameParentLpItemList->list[$i]->id;
13789
                // display_order
13790
                $display_order = $i + 1;
13791
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
13792
                // previous_item_id
13793
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
13794
                $previous_item_id = $item_id;
13795
                // next_item_id
13796
                $next_item_id = 0;
13797
                if ($i < count($sameParentLpItemList->list) - 1) {
13798
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
13799
                }
13800
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
13801
            }
13802
        }
13803
13804
        $table = Database::get_course_table(TABLE_LP_ITEM);
13805
13806
        foreach ($itemList->list as $item) {
13807
            $params = [];
13808
            $params['display_order'] = $item->display_order;
13809
            $params['previous_item_id'] = $item->previous_item_id;
13810
            $params['next_item_id'] = $item->next_item_id;
13811
            $params['parent_item_id'] = $item->parent_item_id;
13812
13813
            Database::update(
13814
                $table,
13815
                $params,
13816
                [
13817
                    'iid = ? AND c_id = ? ' => [
13818
                        (int) $item->id,
13819
                        (int) $courseId,
13820
                    ],
13821
                ]
13822
            );
13823
        }
13824
    }
13825
13826
    /**
13827
     * Get the depth level of LP item.
13828
     *
13829
     * @param array $items
13830
     * @param int   $currentItemId
13831
     *
13832
     * @return int
13833
     */
13834
    private static function get_level_for_item($items, $currentItemId)
13835
    {
13836
        $parentItemId = 0;
13837
        if (isset($items[$currentItemId])) {
13838
            $parentItemId = $items[$currentItemId]->parent;
13839
        }
13840
13841
        if ($parentItemId == 0) {
13842
            return 0;
13843
        } else {
13844
            return self::get_level_for_item($items, $parentItemId) + 1;
13845
        }
13846
    }
13847
13848
    /**
13849
     * Generate the link for a learnpath category as course tool.
13850
     *
13851
     * @param int $categoryId
13852
     *
13853
     * @return string
13854
     */
13855
    private static function getCategoryLinkForTool($categoryId)
13856
    {
13857
        $categoryId = (int) $categoryId;
13858
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13859
            .http_build_query(
13860
                [
13861
                    'action' => 'view_category',
13862
                    'id' => $categoryId,
13863
                ]
13864
            );
13865
13866
        return $link;
13867
    }
13868
13869
    /**
13870
     * Return the scorm item type object with spaces replaced with _
13871
     * The return result is use to build a css classname like scorm_type_$return.
13872
     *
13873
     * @param $in_type
13874
     *
13875
     * @return mixed
13876
     */
13877
    private static function format_scorm_type_item($in_type)
13878
    {
13879
        return str_replace(' ', '_', $in_type);
13880
    }
13881
13882
    /**
13883
     * Check and obtain the lp final item if exist.
13884
     *
13885
     * @return learnpathItem
13886
     */
13887
    private function getFinalItem()
13888
    {
13889
        if (empty($this->items)) {
13890
            return null;
13891
        }
13892
13893
        foreach ($this->items as $item) {
13894
            if ($item->type !== 'final_item') {
13895
                continue;
13896
            }
13897
13898
            return $item;
13899
        }
13900
    }
13901
13902
    /**
13903
     * Get the LP Final Item Template.
13904
     *
13905
     * @return string
13906
     */
13907
    private function getFinalItemTemplate()
13908
    {
13909
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13910
    }
13911
13912
    /**
13913
     * Get the LP Final Item Url.
13914
     *
13915
     * @return string
13916
     */
13917
    private function getSavedFinalItem()
13918
    {
13919
        $finalItem = $this->getFinalItem();
13920
        $doc = DocumentManager::get_document_data_by_id(
13921
            $finalItem->path,
13922
            $this->cc
13923
        );
13924
        if ($doc && file_exists($doc['absolute_path'])) {
13925
            return file_get_contents($doc['absolute_path']);
13926
        }
13927
13928
        return '';
13929
    }
13930
}
13931