Completed
Push — master ( 3b3f82...8690b8 )
by Julito
10:13
created

learnpath   F

Complexity

Total Complexity 1945

Size/Duplication

Total Lines 14098
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7909
dl 0
loc 14098
rs 0.8
c 5
b 1
f 0
wmc 1945

215 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Repository\CourseRepository;
5
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
8
use Chamilo\CourseBundle\Entity\CItemProperty;
9
use Chamilo\CourseBundle\Entity\CLp;
10
use Chamilo\CourseBundle\Entity\CLpCategory;
11
use Chamilo\CourseBundle\Entity\CLpItem;
12
use Chamilo\CourseBundle\Entity\CLpItemView;
13
use Chamilo\CourseBundle\Entity\CTool;
14
use Chamilo\UserBundle\Entity\User;
15
use ChamiloSession as Session;
16
use Gedmo\Sortable\Entity\Repository\SortableRepository;
17
use Symfony\Component\Filesystem\Filesystem;
18
use Symfony\Component\Finder\Finder;
19
20
/**
21
 * Class learnpath
22
 * This class defines the parent attributes and methods for Chamilo learnpaths
23
 * and SCORM learnpaths. It is used by the scorm class.
24
 *
25
 * @todo decouple class
26
 *
27
 * @package chamilo.learnpath
28
 *
29
 * @author  Yannick Warnier <[email protected]>
30
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
31
 */
32
class learnpath
33
{
34
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
35
36
    public $attempt = 0; // The number for the current ID view.
37
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
38
    public $current; // Id of the current item the user is viewing.
39
    public $current_score; // The score of the current item.
40
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
41
    public $current_time_stop; // The time the user closed this resource.
42
    public $default_status = 'not attempted';
43
    public $encoding = 'UTF-8';
44
    public $error = '';
45
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
46
    public $index; // The index of the active learnpath_item in $ordered_items array.
47
    public $items = [];
48
    public $last; // item_id of last item viewed in the learning path.
49
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
50
    public $license; // Which license this course has been given - not used yet on 20060522.
51
    public $lp_id; // DB iid for this learnpath.
52
    public $lp_view_id; // DB ID for lp_view
53
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
54
    public $message = '';
55
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
56
    public $name; // Learnpath name (they generally have one).
57
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
58
    public $path = ''; // Path inside the scorm directory (if scorm).
59
    public $theme; // The current theme of the learning path.
60
    public $preview_image; // The current image of the learning path.
61
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
62
    public $accumulateWorkTime; // The min time of learnpath
63
64
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
65
    public $prevent_reinit = 1;
66
67
    // Describes the mode of progress bar display.
68
    public $seriousgame_mode = 0;
69
    public $progress_bar_mode = '%';
70
71
    // Percentage progress as saved in the db.
72
    public $progress_db = 0;
73
    public $proximity; // Wether the content is distant or local or unknown.
74
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
75
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
76
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
77
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
78
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
79
    public $user_id; //ID of the user that is viewing/using the course
80
    public $update_queue = [];
81
    public $scorm_debug = 0;
82
    public $arrMenu = []; // Array for the menu items.
83
    public $debug = 0; // Logging level.
84
    public $lp_session_id = 0;
85
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
86
    public $prerequisite = 0;
87
    public $use_max_score = 1; // 1 or 0
88
    public $subscribeUsers = 0; // Subscribe users or not
89
    public $created_on = '';
90
    public $modified_on = '';
91
    public $publicated_on = '';
92
    public $expired_on = '';
93
    public $ref = null;
94
    public $course_int_id;
95
    public $course_info = [];
96
    public $categoryId;
97
98
    /**
99
     * Constructor.
100
     * Needs a database handler, a course code and a learnpath id from the database.
101
     * Also builds the list of items into $this->items.
102
     *
103
     * @param string $course  Course code
104
     * @param int    $lp_id   c_lp.iid
105
     * @param int    $user_id
106
     */
107
    public function __construct($course, $lp_id, $user_id)
108
    {
109
        $debug = $this->debug;
110
        $this->encoding = api_get_system_encoding();
111
        if ($debug) {
112
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
113
        }
114
        if (empty($course)) {
115
            $course = api_get_course_id();
116
        }
117
        $course_info = api_get_course_info($course);
118
        if (!empty($course_info)) {
119
            $this->cc = $course_info['code'];
120
            $this->course_info = $course_info;
121
            $course_id = $course_info['real_id'];
122
        } else {
123
            $this->error = 'Course code does not exist in database.';
124
        }
125
126
        $lp_id = (int) $lp_id;
127
        $course_id = (int) $course_id;
128
        $this->set_course_int_id($course_id);
129
        // Check learnpath ID.
130
        if (empty($lp_id) || empty($course_id)) {
131
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
132
        } else {
133
            // TODO: Make it flexible to use any course_code (still using env course code here).
134
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
135
            $sql = "SELECT * FROM $lp_table
136
                    WHERE iid = $lp_id";
137
            if ($debug) {
138
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
139
            }
140
            $res = Database::query($sql);
141
            if (Database::num_rows($res) > 0) {
142
                $this->lp_id = $lp_id;
143
                $row = Database::fetch_array($res);
144
                $this->type = $row['lp_type'];
145
                $this->name = stripslashes($row['name']);
146
                $this->proximity = $row['content_local'];
147
                $this->theme = $row['theme'];
148
                $this->maker = $row['content_maker'];
149
                $this->prevent_reinit = $row['prevent_reinit'];
150
                $this->seriousgame_mode = $row['seriousgame_mode'];
151
                $this->license = $row['content_license'];
152
                $this->scorm_debug = $row['debug'];
153
                $this->js_lib = $row['js_lib'];
154
                $this->path = $row['path'];
155
                $this->preview_image = $row['preview_image'];
156
                $this->author = $row['author'];
157
                $this->hide_toc_frame = $row['hide_toc_frame'];
158
                $this->lp_session_id = $row['session_id'];
159
                $this->use_max_score = $row['use_max_score'];
160
                $this->subscribeUsers = $row['subscribe_users'];
161
                $this->created_on = $row['created_on'];
162
                $this->modified_on = $row['modified_on'];
163
                $this->ref = $row['ref'];
164
                $this->categoryId = $row['category_id'];
165
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
166
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
167
168
                if (!empty($row['publicated_on'])) {
169
                    $this->publicated_on = $row['publicated_on'];
170
                }
171
172
                if (!empty($row['expired_on'])) {
173
                    $this->expired_on = $row['expired_on'];
174
                }
175
                if ($this->type == 2) {
176
                    if ($row['force_commit'] == 1) {
177
                        $this->force_commit = true;
178
                    }
179
                }
180
                $this->mode = $row['default_view_mod'];
181
182
                // Check user ID.
183
                if (empty($user_id)) {
184
                    $this->error = 'User ID is empty';
185
                } else {
186
                    $userInfo = api_get_user_info($user_id);
187
                    if (!empty($userInfo)) {
188
                        $this->user_id = $userInfo['user_id'];
189
                    } else {
190
                        $this->error = 'User ID does not exist in database #'.$user_id;
191
                    }
192
                }
193
194
                // End of variables checking.
195
                $session_id = api_get_session_id();
196
                //  Get the session condition for learning paths of the base + session.
197
                $session = api_get_session_condition($session_id);
198
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
199
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
200
201
                // Selecting by view_count descending allows to get the highest view_count first.
202
                $sql = "SELECT * FROM $lp_table
203
                        WHERE 
204
                            c_id = $course_id AND 
205
                            lp_id = $lp_id AND 
206
                            user_id = $user_id 
207
                            $session
208
                        ORDER BY view_count DESC";
209
                $res = Database::query($sql);
210
                if ($debug) {
211
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
212
                }
213
214
                if (Database::num_rows($res) > 0) {
215
                    if ($debug) {
216
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
217
                    }
218
                    $row = Database::fetch_array($res);
219
                    $this->attempt = $row['view_count'];
220
                    $this->lp_view_id = $row['id'];
221
                    $this->last_item_seen = $row['last_item'];
222
                    $this->progress_db = $row['progress'];
223
                    $this->lp_view_session_id = $row['session_id'];
224
                } elseif (!api_is_invitee()) {
225
                    if ($debug) {
226
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
227
                    }
228
                    $this->attempt = 1;
229
                    $params = [
230
                        'c_id' => $course_id,
231
                        'lp_id' => $lp_id,
232
                        'user_id' => $user_id,
233
                        'view_count' => 1,
234
                        'session_id' => $session_id,
235
                        'last_item' => 0,
236
                    ];
237
                    $this->last_item_seen = 0;
238
                    $this->lp_view_session_id = $session_id;
239
                    $this->lp_view_id = Database::insert($lp_table, $params);
240
                    if (!empty($this->lp_view_id)) {
241
                        $sql = "UPDATE $lp_table SET id = iid
242
                                WHERE iid = ".$this->lp_view_id;
243
                        Database::query($sql);
244
                    }
245
                }
246
247
                // Initialise items.
248
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
249
                $sql = "SELECT * FROM $lp_item_table
250
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
251
                        ORDER BY parent_item_id, display_order";
252
                $res = Database::query($sql);
253
254
                if ($debug) {
255
                    error_log('learnpath::__construct() '.__LINE__.' - query lp items: '.$sql);
256
                    error_log('-- Start while--');
257
                }
258
259
                $lp_item_id_list = [];
260
                while ($row = Database::fetch_array($res)) {
261
                    $lp_item_id_list[] = $row['iid'];
262
                    switch ($this->type) {
263
                        case 3: //aicc
264
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
265
                            if (is_object($oItem)) {
266
                                $my_item_id = $oItem->get_id();
267
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
268
                                $oItem->set_prevent_reinit($this->prevent_reinit);
269
                                // Don't use reference here as the next loop will make the pointed object change.
270
                                $this->items[$my_item_id] = $oItem;
271
                                $this->refs_list[$oItem->ref] = $my_item_id;
272
                                if ($debug) {
273
                                    error_log(
274
                                        'learnpath::__construct() - '.
275
                                        'aicc object with id '.$my_item_id.
276
                                        ' set in items[]',
277
                                        0
278
                                    );
279
                                }
280
                            }
281
                            break;
282
                        case 2:
283
                            $oItem = new scormItem('db', $row['iid'], $course_id);
284
                            if (is_object($oItem)) {
285
                                $my_item_id = $oItem->get_id();
286
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
287
                                $oItem->set_prevent_reinit($this->prevent_reinit);
288
                                // Don't use reference here as the next loop will make the pointed object change.
289
                                $this->items[$my_item_id] = $oItem;
290
                                $this->refs_list[$oItem->ref] = $my_item_id;
291
                                if ($debug) {
292
                                    error_log('object with id '.$my_item_id.' set in items[]');
293
                                }
294
                            }
295
                            break;
296
                        case 1:
297
                        default:
298
                            if ($debug) {
299
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
300
                            }
301
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
302
303
                            if ($debug) {
304
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
305
                            }
306
                            if (is_object($oItem)) {
307
                                $my_item_id = $oItem->get_id();
308
                                // Moved down to when we are sure the item_view exists.
309
                                //$oItem->set_lp_view($this->lp_view_id);
310
                                $oItem->set_prevent_reinit($this->prevent_reinit);
311
                                // Don't use reference here as the next loop will make the pointed object change.
312
                                $this->items[$my_item_id] = $oItem;
313
                                $this->refs_list[$my_item_id] = $my_item_id;
314
                                if ($debug) {
315
                                    error_log(
316
                                        'learnpath::__construct() '.__LINE__.
317
                                        ' - object with id '.$my_item_id.' set in items[]'
318
                                    );
319
                                }
320
                            }
321
                            break;
322
                    }
323
324
                    // Setting the object level with variable $this->items[$i][parent]
325
                    foreach ($this->items as $itemLPObject) {
326
                        $level = self::get_level_for_item(
327
                            $this->items,
328
                            $itemLPObject->db_id
329
                        );
330
                        $itemLPObject->level = $level;
331
                    }
332
333
                    // Setting the view in the item object.
334
                    if (is_object($this->items[$row['iid']])) {
335
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
336
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
337
                            $this->items[$row['iid']]->current_start_time = 0;
338
                            $this->items[$row['iid']]->current_stop_time = 0;
339
                        }
340
                    }
341
                }
342
343
                if ($debug) {
344
                    error_log('learnpath::__construct() '.__LINE__.' ----- end while ----');
345
                }
346
347
                if (!empty($lp_item_id_list)) {
348
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
349
                    if (!empty($lp_item_id_list_to_string)) {
350
                        // Get last viewing vars.
351
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
352
                        // This query should only return one or zero result.
353
                        $sql = "SELECT lp_item_id, status
354
                                FROM $itemViewTable
355
                                WHERE
356
                                    c_id = $course_id AND
357
                                    lp_view_id = ".$this->lp_view_id." AND
358
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
359
                                ORDER BY view_count DESC ";
360
361
                        if ($debug) {
362
                            error_log(
363
                                'learnpath::__construct() - Selecting item_views: '.$sql,
364
                                0
365
                            );
366
                        }
367
368
                        $status_list = [];
369
                        $res = Database::query($sql);
370
                        while ($row = Database:: fetch_array($res)) {
371
                            $status_list[$row['lp_item_id']] = $row['status'];
372
                        }
373
374
                        foreach ($lp_item_id_list as $item_id) {
375
                            if (isset($status_list[$item_id])) {
376
                                $status = $status_list[$item_id];
377
                                if (is_object($this->items[$item_id])) {
378
                                    $this->items[$item_id]->set_status($status);
379
                                    if (empty($status)) {
380
                                        $this->items[$item_id]->set_status(
381
                                            $this->default_status
382
                                        );
383
                                    }
384
                                }
385
                            } else {
386
                                if (!api_is_invitee()) {
387
                                    if (is_object($this->items[$item_id])) {
388
                                        $this->items[$item_id]->set_status(
389
                                            $this->default_status
390
                                        );
391
                                    }
392
393
                                    if (!empty($this->lp_view_id)) {
394
                                        // Add that row to the lp_item_view table so that
395
                                        // we have something to show in the stats page.
396
                                        $params = [
397
                                            'c_id' => $course_id,
398
                                            'lp_item_id' => $item_id,
399
                                            'lp_view_id' => $this->lp_view_id,
400
                                            'view_count' => 1,
401
                                            'status' => 'not attempted',
402
                                            'start_time' => time(),
403
                                            'total_time' => 0,
404
                                            'score' => 0,
405
                                        ];
406
                                        $insertId = Database::insert($itemViewTable, $params);
407
408
                                        if ($insertId) {
409
                                            $sql = "UPDATE $itemViewTable SET id = iid
410
                                                    WHERE iid = $insertId";
411
                                            Database::query($sql);
412
                                        }
413
414
                                        $this->items[$item_id]->set_lp_view(
415
                                            $this->lp_view_id,
416
                                            $course_id
417
                                        );
418
                                    }
419
                                }
420
                            }
421
                        }
422
                    }
423
                }
424
425
                $this->ordered_items = self::get_flat_ordered_items_list(
426
                    $this->get_id(),
427
                    0,
428
                    $course_id
429
                );
430
                $this->max_ordered_items = 0;
431
                foreach ($this->ordered_items as $index => $dummy) {
432
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
433
                        $this->max_ordered_items = $index;
434
                    }
435
                }
436
                // TODO: Define the current item better.
437
                $this->first();
438
                if ($debug) {
439
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
440
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
441
                }
442
            } else {
443
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
444
            }
445
        }
446
    }
447
448
    /**
449
     * @return string
450
     */
451
    public function getCourseCode()
452
    {
453
        return $this->cc;
454
    }
455
456
    /**
457
     * @return int
458
     */
459
    public function get_course_int_id()
460
    {
461
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
462
    }
463
464
    /**
465
     * @param $course_id
466
     *
467
     * @return int
468
     */
469
    public function set_course_int_id($course_id)
470
    {
471
        return $this->course_int_id = (int) $course_id;
472
    }
473
474
    /**
475
     * Function rewritten based on old_add_item() from Yannick Warnier.
476
     * Due the fact that users can decide where the item should come, I had to overlook this function and
477
     * I found it better to rewrite it. Old function is still available.
478
     * Added also the possibility to add a description.
479
     *
480
     * @param int    $parent
481
     * @param int    $previous
482
     * @param string $type
483
     * @param int    $id               resource ID (ref)
484
     * @param string $title
485
     * @param string $description
486
     * @param int    $prerequisites
487
     * @param int    $max_time_allowed
488
     * @param int    $userId
489
     *
490
     * @return int
491
     */
492
    public function add_item(
493
        $parent,
494
        $previous,
495
        $type = 'dir',
496
        $id,
497
        $title,
498
        $description,
499
        $prerequisites = 0,
500
        $max_time_allowed = 0,
501
        $userId = 0
502
    ) {
503
        $course_id = $this->course_info['real_id'];
504
        if ($this->debug > 0) {
505
            error_log('In learnpath::add_item('.$parent.','.$previous.','.$type.','.$id.','.$title.')');
506
        }
507
        if (empty($course_id)) {
508
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
509
            $this->course_info = api_get_course_info($this->cc);
510
            $course_id = $this->course_info['real_id'];
511
        }
512
        $userId = empty($userId) ? api_get_user_id() : $userId;
513
        $sessionId = api_get_session_id();
514
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
515
        $_course = $this->course_info;
516
        $parent = (int) $parent;
517
        $previous = (int) $previous;
518
        $id = (int) $id;
519
        $max_time_allowed = htmlentities($max_time_allowed);
520
        if (empty($max_time_allowed)) {
521
            $max_time_allowed = 0;
522
        }
523
        $sql = "SELECT COUNT(iid) AS num
524
                FROM $tbl_lp_item
525
                WHERE
526
                    c_id = $course_id AND
527
                    lp_id = ".$this->get_id()." AND
528
                    parent_item_id = ".$parent;
529
530
        $res_count = Database::query($sql);
531
        $row = Database::fetch_array($res_count);
532
        $num = $row['num'];
533
534
        $tmp_previous = 0;
535
        $display_order = 0;
536
        $next = 0;
537
        if ($num > 0) {
538
            if (empty($previous)) {
539
                $sql = "SELECT iid, next_item_id, display_order
540
                        FROM $tbl_lp_item
541
                        WHERE
542
                            c_id = $course_id AND
543
                            lp_id = ".$this->get_id()." AND
544
                            parent_item_id = $parent AND
545
                            previous_item_id = 0 OR
546
                            previous_item_id = $parent";
547
                $result = Database::query($sql);
548
                $row = Database::fetch_array($result);
549
                if ($row) {
550
                    $next = $row['iid'];
551
                }
552
            } else {
553
                $previous = (int) $previous;
554
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
555
						FROM $tbl_lp_item
556
                        WHERE
557
                            c_id = $course_id AND
558
                            lp_id = ".$this->get_id()." AND
559
                            id = $previous";
560
                $result = Database::query($sql);
561
                $row = Database::fetch_array($result);
562
                if ($row) {
563
                    $tmp_previous = $row['iid'];
564
                    $next = $row['next_item_id'];
565
                    $display_order = $row['display_order'];
566
                }
567
            }
568
        }
569
570
        $id = (int) $id;
571
        $typeCleaned = Database::escape_string($type);
572
        $max_score = 100;
573
        if ($type === 'quiz') {
574
            $sql = 'SELECT SUM(ponderation)
575
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
576
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
577
                    ON
578
                        quiz_question.id = quiz_rel_question.question_id AND
579
                        quiz_question.c_id = quiz_rel_question.c_id
580
                    WHERE
581
                        quiz_rel_question.exercice_id = '.$id." AND
582
                        quiz_question.c_id = $course_id AND
583
                        quiz_rel_question.c_id = $course_id ";
584
            $rsQuiz = Database::query($sql);
585
            $max_score = Database::result($rsQuiz, 0, 0);
586
587
            // Disabling the exercise if we add it inside a LP
588
            $exercise = new Exercise($course_id);
589
            $exercise->read($id);
590
            $exercise->disable();
591
            $exercise->save();
592
        }
593
594
        $params = [
595
            'c_id' => $course_id,
596
            'lp_id' => $this->get_id(),
597
            'item_type' => $typeCleaned,
598
            'ref' => '',
599
            'title' => $title,
600
            'description' => $description,
601
            'path' => $id,
602
            'max_score' => $max_score,
603
            'parent_item_id' => $parent,
604
            'previous_item_id' => $previous,
605
            'next_item_id' => (int) $next,
606
            'display_order' => $display_order + 1,
607
            'prerequisite' => $prerequisites,
608
            'max_time_allowed' => $max_time_allowed,
609
            'min_score' => 0,
610
            'launch_data' => '',
611
        ];
612
613
        if ($prerequisites != 0) {
614
            $params['prerequisite'] = $prerequisites;
615
        }
616
617
        $new_item_id = Database::insert($tbl_lp_item, $params);
618
        if ($new_item_id) {
619
            if ($this->debug > 2) {
620
                error_log('Inserting dir/chapter: '.$new_item_id, 0);
621
            }
622
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
623
            Database::query($sql);
624
625
            if (!empty($next)) {
626
                $sql = "UPDATE $tbl_lp_item
627
                        SET previous_item_id = $new_item_id 
628
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
629
                Database::query($sql);
630
            }
631
632
            // Update the item that should be before the new item.
633
            if (!empty($tmp_previous)) {
634
                $sql = "UPDATE $tbl_lp_item
635
                        SET next_item_id = $new_item_id
636
                        WHERE c_id = $course_id AND id = $tmp_previous";
637
                Database::query($sql);
638
            }
639
640
            // Update all the items after the new item.
641
            $sql = "UPDATE $tbl_lp_item
642
                        SET display_order = display_order + 1
643
                    WHERE
644
                        c_id = $course_id AND
645
                        lp_id = ".$this->get_id()." AND
646
                        iid <> $new_item_id AND
647
                        parent_item_id = $parent AND
648
                        display_order > $display_order";
649
            Database::query($sql);
650
651
            // Update the item that should come after the new item.
652
            $sql = "UPDATE $tbl_lp_item
653
                    SET ref = $new_item_id
654
                    WHERE c_id = $course_id AND iid = $new_item_id";
655
            Database::query($sql);
656
657
            $sql = "UPDATE $tbl_lp_item
658
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
659
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
660
            Database::query($sql);
661
662
            // Upload audio.
663
            if (!empty($_FILES['mp3']['name'])) {
664
                // Create the audio folder if it does not exist yet.
665
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
666
                if (!is_dir($filepath.'audio')) {
667
                    mkdir(
668
                        $filepath.'audio',
669
                        api_get_permissions_for_new_directories()
670
                    );
671
                    $audio_id = DocumentManager::addDocument(
672
                        $_course,
673
                        '/audio',
674
                        'folder',
675
                        0,
676
                        'audio',
677
                        '',
678
                        0,
679
                        true,
680
                        null,
681
                        $sessionId,
682
                        $userId
683
                    );
684
                }
685
686
                $file_path = handle_uploaded_document(
687
                    $_course,
688
                    $_FILES['mp3'],
689
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
690
                    '/audio',
691
                    $userId,
692
                    '',
693
                    '',
694
                    '',
695
                    '',
696
                    false
697
                );
698
699
                // Getting the filename only.
700
                $file_components = explode('/', $file_path);
701
                $file = $file_components[count($file_components) - 1];
702
703
                // Store the mp3 file in the lp_item table.
704
                $sql = "UPDATE $tbl_lp_item SET
705
                          audio = '".Database::escape_string($file)."'
706
                        WHERE iid = '".intval($new_item_id)."'";
707
                Database::query($sql);
708
            }
709
        }
710
711
        return $new_item_id;
712
    }
713
714
    /**
715
     * Static admin function allowing addition of a learnpath to a course.
716
     *
717
     * @param string $courseCode
718
     * @param string $name
719
     * @param string $description
720
     * @param string $learnpath
721
     * @param string $origin
722
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
723
     * @param string $publicated_on
724
     * @param string $expired_on
725
     * @param int    $categoryId
726
     * @param int    $userId
727
     *
728
     * @return int The new learnpath ID on success, 0 on failure
729
     */
730
    public static function add_lp(
731
        $courseCode,
732
        $name,
733
        $description = '',
734
        $learnpath = 'guess',
735
        $origin = 'zip',
736
        $zipname = '',
737
        $publicated_on = '',
738
        $expired_on = '',
739
        $categoryId = 0,
740
        $userId = 0
741
    ) {
742
        global $charset;
743
744
        if (!empty($courseCode)) {
745
            $courseInfo = api_get_course_info($courseCode);
746
            $course_id = $courseInfo['real_id'];
747
        } else {
748
            $course_id = api_get_course_int_id();
749
            $courseInfo = api_get_course_info();
750
        }
751
752
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
753
        // Check course code exists.
754
        // Check lp_name doesn't exist, otherwise append something.
755
        $i = 0;
756
        $name = Database::escape_string($name);
757
        $categoryId = (int) $categoryId;
758
759
        // Session id.
760
        $session_id = api_get_session_id();
761
        $userId = empty($userId) ? api_get_user_id() : $userId;
762
        $check_name = "SELECT * FROM $tbl_lp
763
                       WHERE c_id = $course_id AND name = '$name'";
764
765
        $res_name = Database::query($check_name);
766
767
        if (empty($publicated_on)) {
768
            $publicated_on = null;
769
        } else {
770
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
771
        }
772
773
        if (empty($expired_on)) {
774
            $expired_on = null;
775
        } else {
776
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
777
        }
778
779
        while (Database::num_rows($res_name)) {
780
            // There is already one such name, update the current one a bit.
781
            $i++;
782
            $name = $name.' - '.$i;
783
            $check_name = "SELECT * FROM $tbl_lp 
784
                           WHERE c_id = $course_id AND name = '$name'";
785
            $res_name = Database::query($check_name);
786
        }
787
        // New name does not exist yet; keep it.
788
        // Escape description.
789
        // Kevin: added htmlentities().
790
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
791
        $type = 1;
792
        switch ($learnpath) {
793
            case 'guess':
794
                break;
795
            case 'dokeos':
796
            case 'chamilo':
797
                $type = 1;
798
                break;
799
            case 'aicc':
800
                break;
801
        }
802
803
        switch ($origin) {
804
            case 'zip':
805
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
806
                break;
807
            case 'manual':
808
            default:
809
                $get_max = "SELECT MAX(display_order) 
810
                            FROM $tbl_lp WHERE c_id = $course_id";
811
                $res_max = Database::query($get_max);
812
                if (Database::num_rows($res_max) < 1) {
813
                    $dsp = 1;
814
                } else {
815
                    $row = Database::fetch_array($res_max);
816
                    $dsp = $row[0] + 1;
817
                }
818
819
                $params = [
820
                    'c_id' => $course_id,
821
                    'lp_type' => $type,
822
                    'name' => $name,
823
                    'description' => $description,
824
                    'path' => '',
825
                    'default_view_mod' => 'embedded',
826
                    'default_encoding' => 'UTF-8',
827
                    'display_order' => $dsp,
828
                    'content_maker' => 'Chamilo',
829
                    'content_local' => 'local',
830
                    'js_lib' => '',
831
                    'session_id' => $session_id,
832
                    'created_on' => api_get_utc_datetime(),
833
                    'modified_on' => api_get_utc_datetime(),
834
                    'publicated_on' => $publicated_on,
835
                    'expired_on' => $expired_on,
836
                    'category_id' => $categoryId,
837
                    'force_commit' => 0,
838
                    'content_license' => '',
839
                    'debug' => 0,
840
                    'theme' => '',
841
                    'preview_image' => '',
842
                    'author' => '',
843
                    'prerequisite' => 0,
844
                    'hide_toc_frame' => 0,
845
                    'seriousgame_mode' => 0,
846
                    'autolaunch' => 0,
847
                    'max_attempts' => 0,
848
                    'subscribe_users' => 0,
849
                    'accumulate_scorm_time' => 1,
850
                ];
851
                $id = Database::insert($tbl_lp, $params);
852
853
                if ($id > 0) {
854
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
855
                    Database::query($sql);
856
857
                    // Insert into item_property.
858
                    api_item_property_update(
859
                        $courseInfo,
860
                        TOOL_LEARNPATH,
861
                        $id,
862
                        'LearnpathAdded',
863
                        $userId
864
                    );
865
                    api_set_default_visibility(
866
                        $id,
867
                        TOOL_LEARNPATH,
868
                        0,
869
                        $courseInfo,
870
                        $session_id,
871
                        $userId
872
                    );
873
874
                    return $id;
875
                }
876
                break;
877
        }
878
    }
879
880
    /**
881
     * Auto completes the parents of an item in case it's been completed or passed.
882
     *
883
     * @param int $item Optional ID of the item from which to look for parents
884
     */
885
    public function autocomplete_parents($item)
886
    {
887
        $debug = $this->debug;
888
889
        if ($debug) {
890
            error_log('Learnpath::autocomplete_parents()');
891
        }
892
893
        if (empty($item)) {
894
            $item = $this->current;
895
        }
896
897
        $currentItem = $this->getItem($item);
898
        if ($currentItem) {
899
            $parent_id = $currentItem->get_parent();
900
            $parent = $this->getItem($parent_id);
901
            if ($parent) {
902
                // if $item points to an object and there is a parent.
903
                if ($debug) {
904
                    error_log(
905
                        'Autocompleting parent of item '.$item.' '.
906
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
907
                        0
908
                    );
909
                }
910
911
                // New experiment including failed and browsed in completed status.
912
                //$current_status = $currentItem->get_status();
913
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
914
                // Fixes chapter auto complete
915
                if (true) {
916
                    // If the current item is completed or passes or succeeded.
917
                    $updateParentStatus = true;
918
                    if ($debug) {
919
                        error_log('Status of current item is alright');
920
                    }
921
922
                    foreach ($parent->get_children() as $childItemId) {
923
                        $childItem = $this->getItem($childItemId);
924
925
                        // If children was not set try to get the info
926
                        if (empty($childItem->db_item_view_id)) {
927
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
928
                        }
929
930
                        // Check all his brothers (parent's children) for completion status.
931
                        if ($childItemId != $item) {
932
                            if ($debug) {
933
                                error_log(
934
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
935
                                    0
936
                                );
937
                            }
938
                            // Trying completing parents of failed and browsed items as well.
939
                            if ($childItem->status_is(
940
                                [
941
                                    'completed',
942
                                    'passed',
943
                                    'succeeded',
944
                                    'browsed',
945
                                    'failed',
946
                                ]
947
                            )
948
                            ) {
949
                                // Keep completion status to true.
950
                                continue;
951
                            } else {
952
                                if ($debug > 2) {
953
                                    error_log(
954
                                        'Found one incomplete child of parent #'.$parent_id.': child #'.$childItemId.' "'.$childItem->get_title().'", is '.$childItem->get_status().' db_item_view_id:#'.$childItem->db_item_view_id,
955
                                        0
956
                                    );
957
                                }
958
                                $updateParentStatus = false;
959
                                break;
960
                            }
961
                        }
962
                    }
963
964
                    if ($updateParentStatus) {
965
                        // If all the children were completed:
966
                        $parent->set_status('completed');
967
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
968
                        // Force the status to "completed"
969
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
970
                        $this->update_queue[$parent->get_id()] = 'completed';
971
                        if ($debug) {
972
                            error_log(
973
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
974
                                print_r($this->update_queue, 1),
975
                                0
976
                            );
977
                        }
978
                        // Recursive call.
979
                        $this->autocomplete_parents($parent->get_id());
980
                    }
981
                }
982
            } else {
983
                if ($debug) {
984
                    error_log("Parent #$parent_id does not exists");
985
                }
986
            }
987
        } else {
988
            if ($debug) {
989
                error_log("#$item is an item that doesn't have parents");
990
            }
991
        }
992
    }
993
994
    /**
995
     * Closes the current resource.
996
     *
997
     * Stops the timer
998
     * Saves into the database if required
999
     * Clears the current resource data from this object
1000
     *
1001
     * @return bool True on success, false on failure
1002
     */
1003
    public function close()
1004
    {
1005
        if ($this->debug > 0) {
1006
            error_log('In learnpath::close()', 0);
1007
        }
1008
        if (empty($this->lp_id)) {
1009
            $this->error = 'Trying to close this learnpath but no ID is set';
1010
1011
            return false;
1012
        }
1013
        $this->current_time_stop = time();
1014
        $this->ordered_items = [];
1015
        $this->index = 0;
1016
        unset($this->lp_id);
1017
        //unset other stuff
1018
        return true;
1019
    }
1020
1021
    /**
1022
     * Static admin function allowing removal of a learnpath.
1023
     *
1024
     * @param array  $courseInfo
1025
     * @param int    $id         Learnpath ID
1026
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1027
     *
1028
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1029
     */
1030
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1031
    {
1032
        $course_id = api_get_course_int_id();
1033
        if (!empty($courseInfo)) {
1034
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1035
        }
1036
1037
        // TODO: Implement a way of getting this to work when the current object is not set.
1038
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1039
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1040
        if (!empty($id) && ($id != $this->lp_id)) {
1041
            return false;
1042
        }
1043
1044
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1045
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1046
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1047
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1048
1049
        // Delete lp item id.
1050
        foreach ($this->items as $lpItemId => $dummy) {
1051
            $sql = "DELETE FROM $lp_item_view
1052
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1053
            Database::query($sql);
1054
        }
1055
1056
        // Proposed by Christophe (nickname: clefevre)
1057
        $sql = "DELETE FROM $lp_item
1058
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1059
        Database::query($sql);
1060
1061
        $sql = "DELETE FROM $lp_view 
1062
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1063
        Database::query($sql);
1064
1065
        self::toggle_publish($this->lp_id, 'i');
1066
1067
        if ($this->type == 2 || $this->type == 3) {
1068
            // This is a scorm learning path, delete the files as well.
1069
            $sql = "SELECT path FROM $lp
1070
                    WHERE iid = ".$this->lp_id;
1071
            $res = Database::query($sql);
1072
            if (Database::num_rows($res) > 0) {
1073
                $row = Database::fetch_array($res);
1074
                $path = $row['path'];
1075
                $sql = "SELECT id FROM $lp
1076
                        WHERE 
1077
                            c_id = $course_id AND
1078
                            path = '$path' AND 
1079
                            iid != ".$this->lp_id;
1080
                $res = Database::query($sql);
1081
                if (Database::num_rows($res) > 0) {
1082
                    // Another learning path uses this directory, so don't delete it.
1083
                    if ($this->debug > 2) {
1084
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1085
                    }
1086
                } else {
1087
                    // No other LP uses that directory, delete it.
1088
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1089
                    // The absolute system path for this course.
1090
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1091
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1092
                        if ($this->debug > 2) {
1093
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1094
                        }
1095
                        // Proposed by Christophe (clefevre).
1096
                        if (strcmp(substr($path, -2), "/.") == 0) {
1097
                            $path = substr($path, 0, -1); // Remove "." at the end.
1098
                        }
1099
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1100
                        rmdirr($course_scorm_dir.$path);
1101
                    }
1102
                }
1103
            }
1104
        }
1105
1106
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1107
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1108
        // Delete tools
1109
        $sql = "DELETE FROM $tbl_tool
1110
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1111
        Database::query($sql);
1112
1113
        $sql = "DELETE FROM $lp 
1114
                WHERE iid = ".$this->lp_id;
1115
        Database::query($sql);
1116
        // Updates the display order of all lps.
1117
        $this->update_display_order();
1118
1119
        api_item_property_update(
1120
            api_get_course_info(),
1121
            TOOL_LEARNPATH,
1122
            $this->lp_id,
1123
            'delete',
1124
            api_get_user_id()
1125
        );
1126
1127
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1128
            api_get_course_id(),
1129
            4,
1130
            $id,
1131
            api_get_session_id()
1132
        );
1133
1134
        if ($link_info !== false) {
1135
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1136
        }
1137
1138
        if (api_get_setting('search_enabled') == 'true') {
1139
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1140
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1141
        }
1142
    }
1143
1144
    /**
1145
     * Removes all the children of one item - dangerous!
1146
     *
1147
     * @param int $id Element ID of which children have to be removed
1148
     *
1149
     * @return int Total number of children removed
1150
     */
1151
    public function delete_children_items($id)
1152
    {
1153
        $course_id = $this->course_info['real_id'];
1154
        if ($this->debug > 0) {
1155
            error_log('In learnpath::delete_children_items('.$id.')', 0);
1156
        }
1157
        $num = 0;
1158
        if (empty($id) || $id != strval(intval($id))) {
1159
            return false;
1160
        }
1161
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1162
        $sql = "SELECT * FROM $lp_item 
1163
                WHERE c_id = ".$course_id." AND parent_item_id = $id";
1164
        $res = Database::query($sql);
1165
        while ($row = Database::fetch_array($res)) {
1166
            $num += $this->delete_children_items($row['iid']);
1167
            $sql = "DELETE FROM $lp_item 
1168
                    WHERE c_id = ".$course_id." AND iid = ".$row['iid'];
1169
            Database::query($sql);
1170
            $num++;
1171
        }
1172
1173
        return $num;
1174
    }
1175
1176
    /**
1177
     * Removes an item from the current learnpath.
1178
     *
1179
     * @param int $id Elem ID (0 if first)
1180
     *
1181
     * @return int Number of elements moved
1182
     *
1183
     * @todo implement resource removal
1184
     */
1185
    public function delete_item($id)
1186
    {
1187
        $course_id = api_get_course_int_id();
1188
        if ($this->debug > 0) {
1189
            error_log('In learnpath::delete_item()', 0);
1190
        }
1191
        // TODO: Implement the resource removal.
1192
        if (empty($id) || $id != strval(intval($id))) {
1193
            return false;
1194
        }
1195
        // First select item to get previous, next, and display order.
1196
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1197
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1198
        $res_sel = Database::query($sql_sel);
1199
        if (Database::num_rows($res_sel) < 1) {
1200
            return false;
1201
        }
1202
        $row = Database::fetch_array($res_sel);
1203
        $previous = $row['previous_item_id'];
1204
        $next = $row['next_item_id'];
1205
        $display = $row['display_order'];
1206
        $parent = $row['parent_item_id'];
1207
        $lp = $row['lp_id'];
1208
        // Delete children items.
1209
        $num = $this->delete_children_items($id);
1210
        if ($this->debug > 2) {
1211
            error_log('learnpath::delete_item() - deleted '.$num.' children of element '.$id, 0);
1212
        }
1213
        // Now delete the item.
1214
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1215
        if ($this->debug > 2) {
1216
            error_log('Deleting item: '.$sql_del, 0);
1217
        }
1218
        Database::query($sql_del);
1219
        // Now update surrounding items.
1220
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1221
                    WHERE iid = $previous";
1222
        Database::query($sql_upd);
1223
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1224
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1225
        Database::query($sql_upd);
1226
        // Now update all following items with new display order.
1227
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1228
                    WHERE 
1229
                        c_id = $course_id AND 
1230
                        lp_id = $lp AND 
1231
                        parent_item_id = $parent AND 
1232
                        display_order > $display";
1233
        Database::query($sql_all);
1234
1235
        //Removing prerequisites since the item will not longer exist
1236
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1237
                    WHERE c_id = $course_id AND prerequisite = $id";
1238
        Database::query($sql_all);
1239
1240
        $sql = "UPDATE $lp_item
1241
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
1242
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1243
        Database::query($sql);
1244
1245
        // Remove from search engine if enabled.
1246
        if (api_get_setting('search_enabled') === 'true') {
1247
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1248
            $sql = 'SELECT * FROM %s 
1249
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1250
                    LIMIT 1';
1251
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1252
            $res = Database::query($sql);
1253
            if (Database::num_rows($res) > 0) {
1254
                $row2 = Database::fetch_array($res);
1255
                $di = new ChamiloIndexer();
1256
                $di->remove_document($row2['search_did']);
1257
            }
1258
            $sql = 'DELETE FROM %s 
1259
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1260
                    LIMIT 1';
1261
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1262
            Database::query($sql);
1263
        }
1264
    }
1265
1266
    /**
1267
     * Updates an item's content in place.
1268
     *
1269
     * @param int    $id               Element ID
1270
     * @param int    $parent           Parent item ID
1271
     * @param int    $previous         Previous item ID
1272
     * @param string $title            Item title
1273
     * @param string $description      Item description
1274
     * @param string $prerequisites    Prerequisites (optional)
1275
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1276
     * @param int    $max_time_allowed
1277
     * @param string $url
1278
     *
1279
     * @return bool True on success, false on error
1280
     */
1281
    public function edit_item(
1282
        $id,
1283
        $parent,
1284
        $previous,
1285
        $title,
1286
        $description,
1287
        $prerequisites = '0',
1288
        $audio = [],
1289
        $max_time_allowed = 0,
1290
        $url = ''
1291
    ) {
1292
        $course_id = api_get_course_int_id();
1293
        $_course = api_get_course_info();
1294
1295
        if ($this->debug > 0) {
1296
            error_log('In learnpath::edit_item()', 0);
1297
        }
1298
        if (empty($max_time_allowed)) {
1299
            $max_time_allowed = 0;
1300
        }
1301
        if (empty($id) || ($id != strval(intval($id))) || empty($title)) {
1302
            return false;
1303
        }
1304
1305
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1306
        $sql = "SELECT * FROM $tbl_lp_item 
1307
                WHERE iid = $id";
1308
        $res_select = Database::query($sql);
1309
        $row_select = Database::fetch_array($res_select);
1310
        $audio_update_sql = '';
1311
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1312
            // Create the audio folder if it does not exist yet.
1313
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1314
            if (!is_dir($filepath.'audio')) {
1315
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1316
                $audio_id = DocumentManager::addDocument(
1317
                    $_course,
1318
                    '/audio',
1319
                    'folder',
1320
                    0,
1321
                    'audio'
1322
                );
1323
            }
1324
1325
            // Upload file in documents.
1326
            $pi = pathinfo($audio['name']);
1327
            if ($pi['extension'] == 'mp3') {
1328
                $c_det = api_get_course_info($this->cc);
1329
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1330
                $path = handle_uploaded_document(
1331
                    $c_det,
1332
                    $audio,
1333
                    $bp,
1334
                    '/audio',
1335
                    api_get_user_id(),
1336
                    0,
1337
                    null,
1338
                    0,
1339
                    'rename',
1340
                    false,
1341
                    0
1342
                );
1343
                $path = substr($path, 7);
1344
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1345
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1346
            }
1347
        }
1348
1349
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1350
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1351
1352
        // TODO: htmlspecialchars to be checked for encoding related problems.
1353
        if ($same_parent && $same_previous) {
1354
            // Only update title and description.
1355
            $sql = "UPDATE $tbl_lp_item
1356
                    SET title = '".Database::escape_string($title)."',
1357
                        prerequisite = '".$prerequisites."',
1358
                        description = '".Database::escape_string($description)."'
1359
                        ".$audio_update_sql.",
1360
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1361
                    WHERE iid = $id";
1362
            Database::query($sql);
1363
        } else {
1364
            $old_parent = $row_select['parent_item_id'];
1365
            $old_previous = $row_select['previous_item_id'];
1366
            $old_next = $row_select['next_item_id'];
1367
            $old_order = $row_select['display_order'];
1368
            $old_prerequisite = $row_select['prerequisite'];
1369
            $old_max_time_allowed = $row_select['max_time_allowed'];
1370
1371
            /* BEGIN -- virtually remove the current item id */
1372
            /* for the next and previous item it is like the current item doesn't exist anymore */
1373
            if ($old_previous != 0) {
1374
                // Next
1375
                $sql = "UPDATE $tbl_lp_item
1376
                        SET next_item_id = $old_next
1377
                        WHERE iid = $old_previous";
1378
                Database::query($sql);
1379
            }
1380
1381
            if (!empty($old_next)) {
1382
                // Previous
1383
                $sql = "UPDATE $tbl_lp_item
1384
                        SET previous_item_id = $old_previous
1385
                        WHERE iid = $old_next";
1386
                Database::query($sql);
1387
            }
1388
1389
            // display_order - 1 for every item with a display_order
1390
            // bigger then the display_order of the current item.
1391
            $sql = "UPDATE $tbl_lp_item
1392
                    SET display_order = display_order - 1
1393
                    WHERE
1394
                        c_id = $course_id AND
1395
                        display_order > $old_order AND
1396
                        lp_id = ".$this->lp_id." AND
1397
                        parent_item_id = $old_parent";
1398
            Database::query($sql);
1399
            /* END -- virtually remove the current item id */
1400
1401
            /* BEGIN -- update the current item id to his new location */
1402
            if ($previous == 0) {
1403
                // Select the data of the item that should come after the current item.
1404
                $sql = "SELECT id, display_order
1405
                        FROM $tbl_lp_item
1406
                        WHERE
1407
                            c_id = $course_id AND
1408
                            lp_id = ".$this->lp_id." AND
1409
                            parent_item_id = $parent AND
1410
                            previous_item_id = $previous";
1411
                $res_select_old = Database::query($sql);
1412
                $row_select_old = Database::fetch_array($res_select_old);
1413
1414
                // If the new parent didn't have children before.
1415
                if (Database::num_rows($res_select_old) == 0) {
1416
                    $new_next = 0;
1417
                    $new_order = 1;
1418
                } else {
1419
                    $new_next = $row_select_old['id'];
1420
                    $new_order = $row_select_old['display_order'];
1421
                }
1422
            } else {
1423
                // Select the data of the item that should come before the current item.
1424
                $sql = "SELECT next_item_id, display_order
1425
                        FROM $tbl_lp_item
1426
                        WHERE iid = $previous";
1427
                $res_select_old = Database::query($sql);
1428
                $row_select_old = Database::fetch_array($res_select_old);
1429
                $new_next = $row_select_old['next_item_id'];
1430
                $new_order = $row_select_old['display_order'] + 1;
1431
            }
1432
1433
            // TODO: htmlspecialchars to be checked for encoding related problems.
1434
            // Update the current item with the new data.
1435
            $sql = "UPDATE $tbl_lp_item
1436
                    SET
1437
                        title = '".Database::escape_string($title)."',
1438
                        description = '".Database::escape_string($description)."',
1439
                        parent_item_id = $parent,
1440
                        previous_item_id = $previous,
1441
                        next_item_id = $new_next,
1442
                        display_order = $new_order
1443
                        $audio_update_sql
1444
                    WHERE iid = $id";
1445
            Database::query($sql);
1446
1447
            if ($previous != 0) {
1448
                // Update the previous item's next_item_id.
1449
                $sql = "UPDATE $tbl_lp_item
1450
                        SET next_item_id = $id
1451
                        WHERE iid = $previous";
1452
                Database::query($sql);
1453
            }
1454
1455
            if (!empty($new_next)) {
1456
                // Update the next item's previous_item_id.
1457
                $sql = "UPDATE $tbl_lp_item
1458
                        SET previous_item_id = $id
1459
                        WHERE iid = $new_next";
1460
                Database::query($sql);
1461
            }
1462
1463
            if ($old_prerequisite != $prerequisites) {
1464
                $sql = "UPDATE $tbl_lp_item
1465
                        SET prerequisite = '$prerequisites'
1466
                        WHERE iid = $id";
1467
                Database::query($sql);
1468
            }
1469
1470
            if ($old_max_time_allowed != $max_time_allowed) {
1471
                // update max time allowed
1472
                $sql = "UPDATE $tbl_lp_item
1473
                        SET max_time_allowed = $max_time_allowed
1474
                        WHERE iid = $id";
1475
                Database::query($sql);
1476
            }
1477
1478
            // Update all the items with the same or a bigger display_order than the current item.
1479
            $sql = "UPDATE $tbl_lp_item
1480
                    SET display_order = display_order + 1
1481
                    WHERE
1482
                       c_id = $course_id AND
1483
                       lp_id = ".$this->get_id()." AND
1484
                       iid <> $id AND
1485
                       parent_item_id = $parent AND
1486
                       display_order >= $new_order";
1487
            Database::query($sql);
1488
        }
1489
1490
        if ($row_select['item_type'] == 'link') {
1491
            $link = new Link();
1492
            $linkId = $row_select['path'];
1493
            $link->updateLink($linkId, $url);
1494
        }
1495
    }
1496
1497
    /**
1498
     * Updates an item's prereq in place.
1499
     *
1500
     * @param int    $id              Element ID
1501
     * @param string $prerequisite_id Prerequisite Element ID
1502
     * @param int    $minScore        Prerequisite min score
1503
     * @param int    $maxScore        Prerequisite max score
1504
     *
1505
     * @return bool True on success, false on error
1506
     */
1507
    public function edit_item_prereq(
1508
        $id,
1509
        $prerequisite_id,
1510
        $minScore = 0,
1511
        $maxScore = 100
1512
    ) {
1513
        if ($this->debug > 0) {
1514
            error_log('In learnpath::edit_item_prereq('.$id.','.$prerequisite_id.','.$minScore.','.$maxScore.')', 0);
1515
        }
1516
1517
        $id = (int) $id;
1518
        $prerequisite_id = (int) $prerequisite_id;
1519
1520
        if (empty($id)) {
1521
            return false;
1522
        }
1523
1524
        if (empty($minScore) || $minScore < 0) {
1525
            $minScore = 0;
1526
        }
1527
1528
        if (empty($maxScore) || $maxScore < 0) {
1529
            $maxScore = 100;
1530
        }
1531
1532
        $minScore = floatval($minScore);
1533
        $maxScore = floatval($maxScore);
1534
1535
        if (empty($prerequisite_id)) {
1536
            $prerequisite_id = 'NULL';
1537
            $minScore = 0;
1538
            $maxScore = 100;
1539
        }
1540
1541
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1542
        $sql = " UPDATE $tbl_lp_item
1543
                 SET
1544
                    prerequisite = $prerequisite_id ,
1545
                    prerequisite_min_score = $minScore ,
1546
                    prerequisite_max_score = $maxScore
1547
                 WHERE iid = $id";
1548
1549
        Database::query($sql);
1550
1551
        return true;
1552
    }
1553
1554
    /**
1555
     * Gets all the chapters belonging to the same parent as the item/chapter given
1556
     * Can also be called as abstract method.
1557
     *
1558
     * @param int $id Item ID
1559
     *
1560
     * @return array A list of all the "brother items" (or an empty array on failure)
1561
     */
1562
    public function getSiblingDirectories($id)
1563
    {
1564
        $course_id = api_get_course_int_id();
1565
        if ($this->debug > 0) {
1566
            error_log('In learnpath::getSiblingDirectories()', 0);
1567
        }
1568
1569
        if (empty($id) || $id != strval(intval($id))) {
1570
            return [];
1571
        }
1572
1573
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1574
        $sql_parent = "SELECT * FROM $lp_item
1575
                       WHERE iid = $id AND item_type='dir'";
1576
        $res_parent = Database::query($sql_parent);
1577
        if (Database::num_rows($res_parent) > 0) {
1578
            $row_parent = Database::fetch_array($res_parent);
1579
            $parent = $row_parent['parent_item_id'];
1580
            $sql = "SELECT * FROM $lp_item
1581
                    WHERE
1582
                        parent_item_id = $parent AND
1583
                        iid = $id AND
1584
                        item_type='dir'
1585
                    ORDER BY display_order";
1586
            $res_bros = Database::query($sql);
1587
1588
            $list = [];
1589
            while ($row_bro = Database::fetch_array($res_bros)) {
1590
                $list[] = $row_bro;
1591
            }
1592
1593
            return $list;
1594
        }
1595
1596
        return [];
1597
    }
1598
1599
    /**
1600
     * Gets all the items belonging to the same parent as the item given
1601
     * Can also be called as abstract method.
1602
     *
1603
     * @param int $id Item ID
1604
     *
1605
     * @return array A list of all the "brother items" (or an empty array on failure)
1606
     */
1607
    public function get_brother_items($id)
1608
    {
1609
        $course_id = api_get_course_int_id();
1610
        if ($this->debug > 0) {
1611
            error_log('In learnpath::get_brother_items('.$id.')', 0);
1612
        }
1613
1614
        if (empty($id) || $id != strval(intval($id))) {
1615
            return [];
1616
        }
1617
1618
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1619
        $sql_parent = "SELECT * FROM $lp_item 
1620
                       WHERE iid = $id";
1621
        $res_parent = Database::query($sql_parent);
1622
        if (Database::num_rows($res_parent) > 0) {
1623
            $row_parent = Database::fetch_array($res_parent);
1624
            $parent = $row_parent['parent_item_id'];
1625
            $sql = "SELECT * FROM $lp_item 
1626
                    WHERE c_id = $course_id AND parent_item_id = $parent
1627
                    ORDER BY display_order";
1628
            $res_bros = Database::query($sql);
1629
            $list = [];
1630
            while ($row_bro = Database::fetch_array($res_bros)) {
1631
                $list[] = $row_bro;
1632
            }
1633
1634
            return $list;
1635
        }
1636
1637
        return [];
1638
    }
1639
1640
    /**
1641
     * Get the specific prefix index terms of this learning path.
1642
     *
1643
     * @param string $prefix
1644
     *
1645
     * @return array Array of terms
1646
     */
1647
    public function get_common_index_terms_by_prefix($prefix)
1648
    {
1649
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1650
        $terms = get_specific_field_values_list_by_prefix(
1651
            $prefix,
1652
            $this->cc,
1653
            TOOL_LEARNPATH,
1654
            $this->lp_id
1655
        );
1656
        $prefix_terms = [];
1657
        if (!empty($terms)) {
1658
            foreach ($terms as $term) {
1659
                $prefix_terms[] = $term['value'];
1660
            }
1661
        }
1662
1663
        return $prefix_terms;
1664
    }
1665
1666
    /**
1667
     * Gets the number of items currently completed.
1668
     *
1669
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1670
     *
1671
     * @return int The number of items currently completed
1672
     */
1673
    public function get_complete_items_count($failedStatusException = false)
1674
    {
1675
        if ($this->debug > 0) {
1676
            error_log('In learnpath::get_complete_items_count()', 0);
1677
        }
1678
        $i = 0;
1679
        $completedStatusList = [
1680
            'completed',
1681
            'passed',
1682
            'succeeded',
1683
            'browsed',
1684
        ];
1685
1686
        if (!$failedStatusException) {
1687
            $completedStatusList[] = 'failed';
1688
        }
1689
1690
        foreach ($this->items as $id => $dummy) {
1691
            // Trying failed and browsed considered "progressed" as well.
1692
            if ($this->items[$id]->status_is($completedStatusList) &&
1693
                $this->items[$id]->get_type() != 'dir'
1694
            ) {
1695
                $i++;
1696
            }
1697
        }
1698
1699
        return $i;
1700
    }
1701
1702
    /**
1703
     * Gets the current item ID.
1704
     *
1705
     * @return int The current learnpath item id
1706
     */
1707
    public function get_current_item_id()
1708
    {
1709
        $current = 0;
1710
        if ($this->debug > 0) {
1711
            error_log('In learnpath::get_current_item_id()', 0);
1712
        }
1713
        if (!empty($this->current)) {
1714
            $current = $this->current;
1715
        }
1716
        if ($this->debug > 2) {
1717
            error_log('In learnpath::get_current_item_id() - Returning '.$current, 0);
1718
        }
1719
1720
        return $current;
1721
    }
1722
1723
    /**
1724
     * Force to get the first learnpath item id.
1725
     *
1726
     * @return int The current learnpath item id
1727
     */
1728
    public function get_first_item_id()
1729
    {
1730
        $current = 0;
1731
        if (is_array($this->ordered_items)) {
1732
            $current = $this->ordered_items[0];
1733
        }
1734
1735
        return $current;
1736
    }
1737
1738
    /**
1739
     * Gets the total number of items available for viewing in this SCORM.
1740
     *
1741
     * @return int The total number of items
1742
     */
1743
    public function get_total_items_count()
1744
    {
1745
        if ($this->debug > 0) {
1746
            error_log('In learnpath::get_total_items_count()', 0);
1747
        }
1748
1749
        return count($this->items);
1750
    }
1751
1752
    /**
1753
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1754
     *
1755
     * @return int The total no-chapters number of items
1756
     */
1757
    public function getTotalItemsCountWithoutDirs()
1758
    {
1759
        if ($this->debug > 0) {
1760
            error_log('In learnpath::getTotalItemsCountWithoutDirs()', 0);
1761
        }
1762
        $total = 0;
1763
        $typeListNotToCount = self::getChapterTypes();
1764
        foreach ($this->items as $temp2) {
1765
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1766
                $total++;
1767
            }
1768
        }
1769
1770
        return $total;
1771
    }
1772
1773
    /**
1774
     *  Sets the first element URL.
1775
     */
1776
    public function first()
1777
    {
1778
        if ($this->debug > 0) {
1779
            error_log('In learnpath::first()', 0);
1780
            error_log('$this->last_item_seen '.$this->last_item_seen);
1781
        }
1782
1783
        // Test if the last_item_seen exists and is not a dir.
1784
        if (count($this->ordered_items) == 0) {
1785
            $this->index = 0;
1786
        }
1787
1788
        if (!empty($this->last_item_seen) &&
1789
            !empty($this->items[$this->last_item_seen]) &&
1790
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1791
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1792
            //&& !$this->items[$this->last_item_seen]->is_done()
1793
        ) {
1794
            if ($this->debug > 2) {
1795
                error_log(
1796
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1797
                    $this->items[$this->last_item_seen]->get_type()
1798
                );
1799
            }
1800
            $index = -1;
1801
            foreach ($this->ordered_items as $myindex => $item_id) {
1802
                if ($item_id == $this->last_item_seen) {
1803
                    $index = $myindex;
1804
                    break;
1805
                }
1806
            }
1807
            if ($index == -1) {
1808
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1809
                if ($this->debug > 2) {
1810
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1811
                }
1812
1813
                return false;
1814
            } else {
1815
                $this->last = $this->last_item_seen;
1816
                $this->current = $this->last_item_seen;
1817
                $this->index = $index;
1818
            }
1819
        } else {
1820
            if ($this->debug > 2) {
1821
                error_log('In learnpath::first() - No last item seen', 0);
1822
            }
1823
            $index = 0;
1824
            // Loop through all ordered items and stop at the first item that is
1825
            // not a directory *and* that has not been completed yet.
1826
            while (!empty($this->ordered_items[$index]) &&
1827
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1828
                (
1829
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1830
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1831
                ) && $index < $this->max_ordered_items) {
1832
                $index++;
1833
            }
1834
1835
            $this->last = $this->current;
1836
            // current is
1837
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1838
            $this->index = $index;
1839
            if ($this->debug > 2) {
1840
                error_log('$index '.$index);
1841
                error_log('In learnpath::first() - No last item seen');
1842
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1843
            }
1844
        }
1845
        if ($this->debug > 2) {
1846
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1847
        }
1848
    }
1849
1850
    /**
1851
     * Gets the information about an item in a format usable as JavaScript to update
1852
     * the JS API by just printing this content into the <head> section of the message frame.
1853
     *
1854
     * @param int $item_id
1855
     *
1856
     * @return string
1857
     */
1858
    public function get_js_info($item_id = 0)
1859
    {
1860
        if ($this->debug > 0) {
1861
            error_log('In learnpath::get_js_info('.$item_id.')', 0);
1862
        }
1863
1864
        $info = '';
1865
        $item_id = intval($item_id);
1866
1867
        if (!empty($item_id) && is_object($this->items[$item_id])) {
1868
            //if item is defined, return values from DB
1869
            $oItem = $this->items[$item_id];
1870
            $info .= '<script language="javascript">';
1871
            $info .= "top.set_score(".$oItem->get_score().");\n";
1872
            $info .= "top.set_max(".$oItem->get_max().");\n";
1873
            $info .= "top.set_min(".$oItem->get_min().");\n";
1874
            $info .= "top.set_lesson_status('".$oItem->get_status()."');";
1875
            $info .= "top.set_session_time('".$oItem->get_scorm_time('js')."');";
1876
            $info .= "top.set_suspend_data('".$oItem->get_suspend_data()."');";
1877
            $info .= "top.set_saved_lesson_status('".$oItem->get_status()."');";
1878
            $info .= "top.set_flag_synchronized();";
1879
            $info .= '</script>';
1880
            if ($this->debug > 2) {
1881
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1882
            }
1883
1884
            return $info;
1885
        } else {
1886
            // If item_id is empty, just update to default SCORM data.
1887
            $info .= '<script language="javascript">';
1888
            $info .= "top.set_score(".learnpathItem::get_score().");\n";
1889
            $info .= "top.set_max(".learnpathItem::get_max().");\n";
1890
            $info .= "top.set_min(".learnpathItem::get_min().");\n";
1891
            $info .= "top.set_lesson_status('".learnpathItem::get_status()."');";
1892
            $info .= "top.set_session_time('".learnpathItem::getScormTimeFromParameter('js')."');";
1893
            $info .= "top.set_suspend_data('".learnpathItem::get_suspend_data()."');";
1894
            $info .= "top.set_saved_lesson_status('".learnpathItem::get_status()."');";
1895
            $info .= "top.set_flag_synchronized();";
1896
            $info .= '</script>';
1897
            if ($this->debug > 2) {
1898
                error_log('in learnpath::get_js_info('.$item_id.') - returning: '.$info, 0);
1899
            }
1900
1901
            return $info;
1902
        }
1903
    }
1904
1905
    /**
1906
     * Gets the js library from the database.
1907
     *
1908
     * @return string The name of the javascript library to be used
1909
     */
1910
    public function get_js_lib()
1911
    {
1912
        $lib = '';
1913
        if (!empty($this->js_lib)) {
1914
            $lib = $this->js_lib;
1915
        }
1916
1917
        return $lib;
1918
    }
1919
1920
    /**
1921
     * Gets the learnpath database ID.
1922
     *
1923
     * @return int Learnpath ID in the lp table
1924
     */
1925
    public function get_id()
1926
    {
1927
        if (!empty($this->lp_id)) {
1928
            return $this->lp_id;
1929
        } else {
1930
            return 0;
1931
        }
1932
    }
1933
1934
    /**
1935
     * Gets the last element URL.
1936
     *
1937
     * @return string URL to load into the viewer
1938
     */
1939
    public function get_last()
1940
    {
1941
        if ($this->debug > 0) {
1942
            error_log('In learnpath::get_last()', 0);
1943
        }
1944
        //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1945
        if (count($this->ordered_items) > 0) {
1946
            $this->index = count($this->ordered_items) - 1;
1947
1948
            return $this->ordered_items[$this->index];
1949
        }
1950
1951
        return false;
1952
    }
1953
1954
    /**
1955
     * Get the last element in the first level.
1956
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1957
     *
1958
     * @return mixed
1959
     */
1960
    public function getLastInFirstLevel()
1961
    {
1962
        try {
1963
            $lastId = Database::getManager()
1964
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1965
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1966
                ->setMaxResults(1)
1967
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1968
                ->getSingleScalarResult();
1969
1970
            return $lastId;
1971
        } catch (Exception $exception) {
1972
            return 0;
1973
        }
1974
    }
1975
1976
    /**
1977
     * Gets the navigation bar for the learnpath display screen.
1978
     *
1979
     * @return string The HTML string to use as a navigation bar
1980
     */
1981
    public function get_navigation_bar($idBar = null, $display = null)
1982
    {
1983
        if ($this->debug > 0) {
1984
            error_log('In learnpath::get_navigation_bar()', 0);
1985
        }
1986
        if (empty($idBar)) {
1987
            $idBar = 'control-top';
1988
        }
1989
        $lpId = $this->lp_id;
1990
        $mycurrentitemid = $this->get_current_item_id();
1991
1992
        $reportingText = get_lang('Reporting');
1993
        $previousText = get_lang('ScormPrevious');
1994
        $nextText = get_lang('ScormNext');
1995
        $fullScreenText = get_lang('ScormExitFullScreen');
1996
1997
        $settings = api_get_configuration_value('lp_view_settings');
1998
        $display = isset($settings['display']) ? $settings['display'] : false;
1999
        $reportingIcon = '
2000
            <a class="icon-toolbar" 
2001
                id="stats_link"
2002
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
2003
                onclick="window.parent.API.save_asset(); return true;" 
2004
                target="content_name" title="'.$reportingText.'">
2005
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
2006
            </a>';
2007
2008
        if (!empty($display)) {
2009
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
2010
            if ($showReporting == false) {
2011
                $reportingIcon = '';
2012
            }
2013
        }
2014
2015
        $hideArrows = false;
2016
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
2017
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
2018
        }
2019
2020
        $previousIcon = '';
2021
        $nextIcon = '';
2022
        if ($hideArrows === false) {
2023
            $previousIcon = '
2024
                <a class="icon-toolbar" id="scorm-previous" href="#" 
2025
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
2026
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
2027
                </a>';
2028
2029
            $nextIcon = '
2030
                <a class="icon-toolbar" id="scorm-next" href="#" 
2031
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
2032
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
2033
                </a>';
2034
        }
2035
2036
        if ($this->mode === 'fullscreen') {
2037
            $navbar = '
2038
                  <span id="'.$idBar.'" class="buttons">
2039
                    '.$reportingIcon.'
2040
                    '.$previousIcon.'                    
2041
                    '.$nextIcon.'
2042
                    <a class="icon-toolbar" id="view-embedded" 
2043
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
2044
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
2045
                    </a>
2046
                  </span>';
2047
        } else {
2048
            $navbar = '
2049
            <span id="'.$idBar.'" class="buttons text-right">
2050
                '.$reportingIcon.'
2051
                '.$previousIcon.'
2052
                '.$nextIcon.'               
2053
            </span>';
2054
        }
2055
2056
        return $navbar;
2057
    }
2058
2059
    /**
2060
     * Gets the next resource in queue (url).
2061
     *
2062
     * @return string URL to load into the viewer
2063
     */
2064
    public function get_next_index()
2065
    {
2066
        if ($this->debug > 0) {
2067
            error_log('In learnpath::get_next_index()', 0);
2068
        }
2069
        // TODO
2070
        $index = $this->index;
2071
        $index++;
2072
        if ($this->debug > 2) {
2073
            error_log('Now looking at ordered_items['.($index).'] - type is '.$this->items[$this->ordered_items[$index]]->type, 0);
2074
        }
2075
        while (
2076
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
2077
            $index < $this->max_ordered_items
2078
        ) {
2079
            $index++;
2080
            if ($index == $this->max_ordered_items) {
2081
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
2082
                    return $this->index;
2083
                } else {
2084
                    return $index;
2085
                }
2086
            }
2087
        }
2088
        if (empty($this->ordered_items[$index])) {
2089
            return $this->index;
2090
        }
2091
        if ($this->debug > 2) {
2092
            error_log('index is now '.$index, 0);
2093
        }
2094
2095
        return $index;
2096
    }
2097
2098
    /**
2099
     * Gets item_id for the next element.
2100
     *
2101
     * @return int Next item (DB) ID
2102
     */
2103
    public function get_next_item_id()
2104
    {
2105
        if ($this->debug > 0) {
2106
            error_log('In learnpath::get_next_item_id()', 0);
2107
        }
2108
        $new_index = $this->get_next_index();
2109
        if (!empty($new_index)) {
2110
            if (isset($this->ordered_items[$new_index])) {
2111
                if ($this->debug > 2) {
2112
                    error_log('In learnpath::get_next_index() - Returning '.$this->ordered_items[$new_index], 0);
2113
                }
2114
2115
                return $this->ordered_items[$new_index];
2116
            }
2117
        }
2118
        if ($this->debug > 2) {
2119
            error_log('In learnpath::get_next_index() - Problem - Returning 0', 0);
2120
        }
2121
2122
        return 0;
2123
    }
2124
2125
    /**
2126
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
2127
     *
2128
     * Generally, the package provided is in the form of a zip file, so the function
2129
     * has been written to test a zip file. If not a zip, the function will return the
2130
     * default return value: ''
2131
     *
2132
     * @param string $file_path the path to the file
2133
     * @param string $file_name the original name of the file
2134
     *
2135
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
2136
     */
2137
    public static function get_package_type($file_path, $file_name)
2138
    {
2139
        // Get name of the zip file without the extension.
2140
        $file_info = pathinfo($file_name);
2141
        $extension = $file_info['extension']; // Extension only.
2142
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
2143
                'dll',
2144
                'exe',
2145
            ])) {
2146
            return 'oogie';
2147
        }
2148
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
2149
                'dll',
2150
                'exe',
2151
            ])) {
2152
            return 'woogie';
2153
        }
2154
2155
        $zipFile = new PclZip($file_path);
2156
        // Check the zip content (real size and file extension).
2157
        $zipContentArray = $zipFile->listContent();
2158
        $package_type = '';
2159
        $manifest = '';
2160
        $aicc_match_crs = 0;
2161
        $aicc_match_au = 0;
2162
        $aicc_match_des = 0;
2163
        $aicc_match_cst = 0;
2164
2165
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2166
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
2167
            foreach ($zipContentArray as $thisContent) {
2168
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2169
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2170
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2171
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2172
                    $package_type = 'scorm';
2173
                    break; // Exit the foreach loop.
2174
                } elseif (
2175
                    preg_match('/aicc\//i', $thisContent['filename']) ||
2176
                    in_array(
2177
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2178
                        ['crs', 'au', 'des', 'cst']
2179
                    )
2180
                ) {
2181
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2182
                    switch ($ext) {
2183
                        case 'crs':
2184
                            $aicc_match_crs = 1;
2185
                            break;
2186
                        case 'au':
2187
                            $aicc_match_au = 1;
2188
                            break;
2189
                        case 'des':
2190
                            $aicc_match_des = 1;
2191
                            break;
2192
                        case 'cst':
2193
                            $aicc_match_cst = 1;
2194
                            break;
2195
                        default:
2196
                            break;
2197
                    }
2198
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2199
                } else {
2200
                    $package_type = '';
2201
                }
2202
            }
2203
        }
2204
2205
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2206
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2207
            $package_type = 'aicc';
2208
        }
2209
2210
        // Try with chamilo course builder
2211
        if (empty($package_type)) {
2212
            $package_type = 'chamilo';
2213
        }
2214
2215
        return $package_type;
2216
    }
2217
2218
    /**
2219
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2220
     *
2221
     * @return string URL to load into the viewer
2222
     */
2223
    public function get_previous_index()
2224
    {
2225
        if ($this->debug > 0) {
2226
            error_log('In learnpath::get_previous_index()', 0);
2227
        }
2228
        $index = $this->index;
2229
        if (isset($this->ordered_items[$index - 1])) {
2230
            $index--;
2231
            while (isset($this->ordered_items[$index]) &&
2232
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2233
            ) {
2234
                $index--;
2235
                if ($index < 0) {
2236
                    return $this->index;
2237
                }
2238
            }
2239
        } else {
2240
            if ($this->debug > 2) {
2241
                error_log('get_previous_index() - there was no previous index available, reusing '.$index, 0);
2242
            }
2243
            // There is no previous item.
2244
        }
2245
2246
        return $index;
2247
    }
2248
2249
    /**
2250
     * Gets item_id for the next element.
2251
     *
2252
     * @return int Previous item (DB) ID
2253
     */
2254
    public function get_previous_item_id()
2255
    {
2256
        if ($this->debug > 0) {
2257
            error_log('In learnpath::get_previous_item_id()', 0);
2258
        }
2259
        $new_index = $this->get_previous_index();
2260
2261
        return $this->ordered_items[$new_index];
2262
    }
2263
2264
    /**
2265
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2266
     *
2267
     * @param int    $lpItemId
2268
     * @param string $autostart
2269
     *
2270
     * @return string The mediaplayer HTML
2271
     */
2272
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2273
    {
2274
        $course_id = api_get_course_int_id();
2275
        $_course = api_get_course_info();
2276
        if (empty($_course)) {
2277
            return '';
2278
        }
2279
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2280
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2281
        $lpItemId = (int) $lpItemId;
2282
2283
        /** @var learnpathItem $item */
2284
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2285
        $itemViewId = 0;
2286
        if ($item) {
2287
            $itemViewId = (int) $item->db_item_view_id;
2288
        }
2289
2290
        // Getting all the information about the item.
2291
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status 
2292
                FROM $tbl_lp_item as lpi
2293
                INNER JOIN $tbl_lp_item_view as lp_view
2294
                ON (lpi.iid = lp_view.lp_item_id)
2295
                WHERE
2296
                    lp_view.iid = $itemViewId AND
2297
                    lpi.iid = $lpItemId AND
2298
                    lp_view.c_id = $course_id";
2299
        $result = Database::query($sql);
2300
        $row = Database::fetch_assoc($result);
2301
        $output = '';
2302
2303
        if (!empty($row['audio'])) {
2304
            $list = $_SESSION['oLP']->get_toc();
2305
2306
            switch ($row['item_type']) {
2307
                case 'quiz':
2308
                    $type_quiz = false;
2309
                    foreach ($list as $toc) {
2310
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2311
                            $type_quiz = true;
2312
                        }
2313
                    }
2314
2315
                    if ($type_quiz) {
2316
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2317
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2318
                        } else {
2319
                            $autostart_audio = $autostart;
2320
                        }
2321
                    }
2322
                    break;
2323
                case TOOL_READOUT_TEXT:;
2324
                    $autostart_audio = 'false';
2325
                    break;
2326
                default:
2327
                    $autostart_audio = 'true';
2328
            }
2329
2330
            $courseInfo = api_get_course_info();
2331
            $audio = $row['audio'];
2332
2333
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2334
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2335
2336
            if (!file_exists($file)) {
2337
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2338
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2339
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2340
            }
2341
2342
            $player = Display::getMediaPlayer(
2343
                $file,
2344
                [
2345
                    'id' => 'lp_audio_media_player',
2346
                    'url' => $url,
2347
                    'autoplay' => $autostart_audio,
2348
                    'width' => '100%',
2349
                ]
2350
            );
2351
2352
            // The mp3 player.
2353
            $output = '<div id="container">';
2354
            $output .= $player;
2355
            $output .= '</div>';
2356
        }
2357
2358
        return $output;
2359
    }
2360
2361
    /**
2362
     * @param int   $studentId
2363
     * @param int   $prerequisite
2364
     * @param array $courseInfo
2365
     * @param int   $sessionId
2366
     *
2367
     * @return bool
2368
     */
2369
    public static function isBlockedByPrerequisite(
2370
        $studentId,
2371
        $prerequisite,
2372
        $courseInfo,
2373
        $sessionId
2374
    ) {
2375
        if (empty($courseInfo)) {
2376
            return false;
2377
        }
2378
2379
        $courseId = $courseInfo['real_id'];
2380
2381
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2382
        if ($allow) {
2383
            if (api_is_allowed_to_edit() ||
2384
                api_is_platform_admin(true) ||
2385
                api_is_drh() ||
2386
                api_is_coach($sessionId, $courseId, false)
2387
            ) {
2388
                return false;
2389
            }
2390
        }
2391
2392
        $isBlocked = false;
2393
        if (!empty($prerequisite)) {
2394
            $progress = self::getProgress(
2395
                $prerequisite,
2396
                $studentId,
2397
                $courseId,
2398
                $sessionId
2399
            );
2400
            if ($progress < 100) {
2401
                $isBlocked = true;
2402
            }
2403
2404
            if (Tracking::minimunTimeAvailable($sessionId, $courseId)) {
2405
                // Block if it does not exceed minimum time
2406
                // Minimum time (in minutes) to pass the learning path
2407
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2408
2409
                if ($accumulateWorkTime > 0) {
2410
                    // Total time in course (sum of times in learning paths from course)
2411
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2412
2413
                    // Connect with the plugin_licences_course_session table
2414
                    // which indicates what percentage of the time applies
2415
                    // Minimum connection percentage
2416
                    $perc = 100;
2417
                    // Time from the course
2418
                    $tc = $accumulateWorkTimeTotal;
2419
2420
                    // Percentage of the learning paths
2421
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2422
                    // Minimum time for each learning path
2423
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2424
2425
                    // Spent time (in seconds) so far in the learning path
2426
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2427
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2428
2429
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2430
                        $isBlocked = true;
2431
                    }
2432
                }
2433
            }
2434
        }
2435
2436
        return $isBlocked;
2437
    }
2438
2439
    /**
2440
     * Checks if the learning path is visible for student after the progress
2441
     * of its prerequisite is completed, considering the time availability and
2442
     * the LP visibility.
2443
     *
2444
     * @param int  $lp_id
2445
     * @param int  $student_id
2446
     * @param null $courseCode
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $courseCode is correct as it would always require null to be passed?
Loading history...
2447
     * @param int  $sessionId
2448
     *
2449
     * @return bool
2450
     */
2451
    public static function is_lp_visible_for_student(
2452
        $lp_id,
2453
        $student_id,
2454
        $courseCode = null,
2455
        $sessionId = 0
2456
    ) {
2457
        $courseInfo = api_get_course_info($courseCode);
2458
        $lp_id = (int) $lp_id;
2459
        $sessionId = (int) $sessionId;
2460
2461
        if (empty($courseInfo)) {
2462
            return false;
2463
        }
2464
2465
        if (empty($sessionId)) {
2466
            $sessionId = api_get_session_id();
2467
        }
2468
2469
        $itemInfo = api_get_item_property_info(
2470
            $courseInfo['real_id'],
2471
            TOOL_LEARNPATH,
2472
            $lp_id,
2473
            $sessionId
2474
        );
2475
2476
        // If the item was deleted.
2477
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2478
            return false;
2479
        }
2480
2481
        // @todo remove this query and load the row info as a parameter
2482
        $table = Database::get_course_table(TABLE_LP_MAIN);
2483
        // Get current prerequisite
2484
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2485
                FROM $table
2486
                WHERE iid = $lp_id";
2487
        $rs = Database::query($sql);
2488
        $now = time();
2489
        if (Database::num_rows($rs) > 0) {
2490
            $row = Database::fetch_array($rs, 'ASSOC');
2491
            $prerequisite = $row['prerequisite'];
2492
            $is_visible = true;
2493
2494
            $isBlocked = self::isBlockedByPrerequisite(
2495
                $student_id,
2496
                $prerequisite,
2497
                $courseInfo,
2498
                $sessionId
2499
            );
2500
2501
            if ($isBlocked) {
2502
                $is_visible = false;
2503
            }
2504
2505
            // Also check the time availability of the LP
2506
            if ($is_visible) {
2507
                // Adding visibility restrictions
2508
                if (!empty($row['publicated_on'])) {
2509
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2510
                        $is_visible = false;
2511
                    }
2512
                }
2513
                // Blocking empty start times see BT#2800
2514
                global $_custom;
2515
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2516
                    $_custom['lps_hidden_when_no_start_date']
2517
                ) {
2518
                    if (empty($row['publicated_on'])) {
2519
                        $is_visible = false;
2520
                    }
2521
                }
2522
2523
                if (!empty($row['expired_on'])) {
2524
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2525
                        $is_visible = false;
2526
                    }
2527
                }
2528
            }
2529
2530
            if ($is_visible) {
2531
                $subscriptionSettings = self::getSubscriptionSettings();
2532
2533
                // Check if the subscription users/group to a LP is ON
2534
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2535
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2536
                ) {
2537
                    // Try group
2538
                    $is_visible = false;
2539
                    // Checking only the user visibility
2540
                    $userVisibility = api_get_item_visibility(
2541
                        $courseInfo,
2542
                        'learnpath',
2543
                        $row['id'],
2544
                        $sessionId,
2545
                        $student_id,
2546
                        'LearnpathSubscription'
2547
                    );
2548
2549
                    if ($userVisibility == 1) {
2550
                        $is_visible = true;
2551
                    } else {
2552
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id);
2553
                        if (!empty($userGroups)) {
2554
                            foreach ($userGroups as $groupInfo) {
2555
                                $groupId = $groupInfo['iid'];
2556
                                $userVisibility = api_get_item_visibility(
2557
                                    $courseInfo,
2558
                                    'learnpath',
2559
                                    $row['id'],
2560
                                    $sessionId,
2561
                                    null,
2562
                                    'LearnpathSubscription',
2563
                                    $groupId
2564
                                );
2565
2566
                                if ($userVisibility == 1) {
2567
                                    $is_visible = true;
2568
                                    break;
2569
                                }
2570
                            }
2571
                        }
2572
                    }
2573
                }
2574
            }
2575
2576
            return $is_visible;
2577
        }
2578
2579
        return false;
2580
    }
2581
2582
    /**
2583
     * @param int $lpId
2584
     * @param int $userId
2585
     * @param int $courseId
2586
     * @param int $sessionId
2587
     *
2588
     * @return int
2589
     */
2590
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2591
    {
2592
        $lpId = (int) $lpId;
2593
        $userId = (int) $userId;
2594
        $courseId = (int) $courseId;
2595
        $sessionId = (int) $sessionId;
2596
2597
        $sessionCondition = api_get_session_condition($sessionId);
2598
        $table = Database::get_course_table(TABLE_LP_VIEW);
2599
        $sql = "SELECT * FROM $table
2600
                WHERE
2601
                    c_id = $courseId AND
2602
                    lp_id = $lpId AND
2603
                    user_id = $userId $sessionCondition ";
2604
        $res = Database::query($sql);
2605
2606
        $progress = 0;
2607
        if (Database::num_rows($res) > 0) {
2608
            $row = Database:: fetch_array($res);
2609
            $progress = (int) $row['progress'];
2610
        }
2611
2612
        return $progress;
2613
    }
2614
2615
    /**
2616
     * Displays a progress bar
2617
     * completed so far.
2618
     *
2619
     * @param int    $percentage Progress value to display
2620
     * @param string $text_add   Text to display near the progress value
2621
     *
2622
     * @return string HTML string containing the progress bar
2623
     */
2624
    public static function get_progress_bar($percentage = -1, $text_add = '')
2625
    {
2626
        $text = $percentage.$text_add;
2627
        $output = '<div class="progress">
2628
            <div id="progress_bar_value" 
2629
                class="progress-bar progress-bar-success" role="progressbar" 
2630
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2631
            '.$text.'
2632
            </div>
2633
        </div>';
2634
2635
        return $output;
2636
    }
2637
2638
    /**
2639
     * @param string $mode can be '%' or 'abs'
2640
     *                     otherwise this value will be used $this->progress_bar_mode
2641
     *
2642
     * @return string
2643
     */
2644
    public function getProgressBar($mode = null)
2645
    {
2646
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2647
2648
        return self::get_progress_bar($percentage, $text_add);
2649
    }
2650
2651
    /**
2652
     * Gets the progress bar info to display inside the progress bar.
2653
     * Also used by scorm_api.php.
2654
     *
2655
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2656
     *                     we display a number of completed elements per total elements
2657
     * @param int    $add  Additional steps to fake as completed
2658
     *
2659
     * @return array Percentage or number and symbol (% or /xx)
2660
     */
2661
    public function get_progress_bar_text($mode = '', $add = 0)
2662
    {
2663
        if ($this->debug > 0) {
2664
            error_log('In learnpath::get_progress_bar_text()', 0);
2665
        }
2666
        if (empty($mode)) {
2667
            $mode = $this->progress_bar_mode;
2668
        }
2669
        $total_items = $this->getTotalItemsCountWithoutDirs();
2670
        if ($this->debug > 2) {
2671
            error_log('Total items available in this learnpath: '.$total_items, 0);
2672
        }
2673
        $completeItems = $this->get_complete_items_count();
2674
        if ($this->debug > 2) {
2675
            error_log('Items completed so far: '.$completeItems, 0);
2676
        }
2677
        if ($add != 0) {
2678
            $completeItems += $add;
2679
            if ($this->debug > 2) {
2680
                error_log('Items completed so far (+modifier): '.$completeItems, 0);
2681
            }
2682
        }
2683
        $text = '';
2684
        if ($completeItems > $total_items) {
2685
            $completeItems = $total_items;
2686
        }
2687
        $percentage = 0;
2688
        if ($mode == '%') {
2689
            if ($total_items > 0) {
2690
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2691
            } else {
2692
                $percentage = 0;
2693
            }
2694
            $percentage = number_format($percentage, 0);
2695
            $text = '%';
2696
        } elseif ($mode == 'abs') {
2697
            $percentage = $completeItems;
2698
            $text = '/'.$total_items;
2699
        }
2700
2701
        return [
2702
            $percentage,
2703
            $text,
2704
        ];
2705
    }
2706
2707
    /**
2708
     * Gets the progress bar mode.
2709
     *
2710
     * @return string The progress bar mode attribute
2711
     */
2712
    public function get_progress_bar_mode()
2713
    {
2714
        if ($this->debug > 0) {
2715
            error_log('In learnpath::get_progress_bar_mode()', 0);
2716
        }
2717
        if (!empty($this->progress_bar_mode)) {
2718
            return $this->progress_bar_mode;
2719
        } else {
2720
            return '%';
2721
        }
2722
    }
2723
2724
    /**
2725
     * Gets the learnpath theme (remote or local).
2726
     *
2727
     * @return string Learnpath theme
2728
     */
2729
    public function get_theme()
2730
    {
2731
        if ($this->debug > 0) {
2732
            error_log('In learnpath::get_theme()', 0);
2733
        }
2734
        if (!empty($this->theme)) {
2735
            return $this->theme;
2736
        } else {
2737
            return '';
2738
        }
2739
    }
2740
2741
    /**
2742
     * Gets the learnpath session id.
2743
     *
2744
     * @return int
2745
     */
2746
    public function get_lp_session_id()
2747
    {
2748
        if ($this->debug > 0) {
2749
            error_log('In learnpath::get_lp_session_id()', 0);
2750
        }
2751
        if (!empty($this->lp_session_id)) {
2752
            return (int) $this->lp_session_id;
2753
        } else {
2754
            return 0;
2755
        }
2756
    }
2757
2758
    /**
2759
     * Gets the learnpath image.
2760
     *
2761
     * @return string Web URL of the LP image
2762
     */
2763
    public function get_preview_image()
2764
    {
2765
        if ($this->debug > 0) {
2766
            error_log('In learnpath::get_preview_image()', 0);
2767
        }
2768
        if (!empty($this->preview_image)) {
2769
            return $this->preview_image;
2770
        } else {
2771
            return '';
2772
        }
2773
    }
2774
2775
    /**
2776
     * @param string $size
2777
     * @param string $path_type
2778
     *
2779
     * @return bool|string
2780
     */
2781
    public function get_preview_image_path($size = null, $path_type = 'web')
2782
    {
2783
        $preview_image = $this->get_preview_image();
2784
        if (isset($preview_image) && !empty($preview_image)) {
2785
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2786
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2787
2788
            if (isset($size)) {
2789
                $info = pathinfo($preview_image);
2790
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2791
2792
                if (file_exists($image_sys_path.$image_custom_size)) {
2793
                    if ($path_type == 'web') {
2794
                        return $image_path.$image_custom_size;
2795
                    } else {
2796
                        return $image_sys_path.$image_custom_size;
2797
                    }
2798
                }
2799
            } else {
2800
                if ($path_type == 'web') {
2801
                    return $image_path.$preview_image;
2802
                } else {
2803
                    return $image_sys_path.$preview_image;
2804
                }
2805
            }
2806
        }
2807
2808
        return false;
2809
    }
2810
2811
    /**
2812
     * Gets the learnpath author.
2813
     *
2814
     * @return string LP's author
2815
     */
2816
    public function get_author()
2817
    {
2818
        if ($this->debug > 0) {
2819
            error_log('In learnpath::get_author()', 0);
2820
        }
2821
        if (!empty($this->author)) {
2822
            return $this->author;
2823
        } else {
2824
            return '';
2825
        }
2826
    }
2827
2828
    /**
2829
     * Gets hide table of contents.
2830
     *
2831
     * @return int
2832
     */
2833
    public function getHideTableOfContents()
2834
    {
2835
        return (int) $this->hide_toc_frame;
2836
    }
2837
2838
    /**
2839
     * Generate a new prerequisites string for a given item. If this item was a sco and
2840
     * its prerequisites were strings (instead of IDs), then transform those strings into
2841
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2842
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2843
     * same rule as the scormExport() method.
2844
     *
2845
     * @param int $item_id Item ID
2846
     *
2847
     * @return string Prerequisites string ready for the export as SCORM
2848
     */
2849
    public function get_scorm_prereq_string($item_id)
2850
    {
2851
        if ($this->debug > 0) {
2852
            error_log('In learnpath::get_scorm_prereq_string()');
2853
        }
2854
        if (!is_object($this->items[$item_id])) {
2855
            return false;
2856
        }
2857
        /** @var learnpathItem $oItem */
2858
        $oItem = $this->items[$item_id];
2859
        $prereq = $oItem->get_prereq_string();
2860
2861
        if (empty($prereq)) {
2862
            return '';
2863
        }
2864
        if (preg_match('/^\d+$/', $prereq) &&
2865
            isset($this->items[$prereq]) &&
2866
            is_object($this->items[$prereq])
2867
        ) {
2868
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2869
            // then simply return it (with the ITEM_ prefix).
2870
            //return 'ITEM_' . $prereq;
2871
            return $this->items[$prereq]->ref;
2872
        } else {
2873
            if (isset($this->refs_list[$prereq])) {
2874
                // It's a simple string item from which the ID can be found in the refs list,
2875
                // so we can transform it directly to an ID for export.
2876
                return $this->items[$this->refs_list[$prereq]]->ref;
2877
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2878
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2879
            } else {
2880
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2881
                // and replace them, one by one, by the internal IDs (chamilo db)
2882
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2883
                // by a space as well.
2884
                $find = [
2885
                    '&',
2886
                    '|',
2887
                    '~',
2888
                    '=',
2889
                    '<>',
2890
                    '{',
2891
                    '}',
2892
                    '*',
2893
                    '(',
2894
                    ')',
2895
                ];
2896
                $replace = [
2897
                    ' ',
2898
                    ' ',
2899
                    ' ',
2900
                    ' ',
2901
                    ' ',
2902
                    ' ',
2903
                    ' ',
2904
                    ' ',
2905
                    ' ',
2906
                    ' ',
2907
                ];
2908
                $prereq_mod = str_replace($find, $replace, $prereq);
2909
                $ids = explode(' ', $prereq_mod);
2910
                foreach ($ids as $id) {
2911
                    $id = trim($id);
2912
                    if (isset($this->refs_list[$id])) {
2913
                        $prereq = preg_replace(
2914
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2915
                            'ITEM_'.$this->refs_list[$id],
2916
                            $prereq
2917
                        );
2918
                    }
2919
                }
2920
2921
                return $prereq;
2922
            }
2923
        }
2924
    }
2925
2926
    /**
2927
     * Returns the XML DOM document's node.
2928
     *
2929
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2930
     * @param string   $id       The identifier to look for
2931
     *
2932
     * @return mixed The reference to the element found with that identifier. False if not found
2933
     */
2934
    public function get_scorm_xml_node(&$children, $id)
2935
    {
2936
        for ($i = 0; $i < $children->length; $i++) {
2937
            $item_temp = $children->item($i);
2938
            if ($item_temp->nodeName == 'item') {
2939
                if ($item_temp->getAttribute('identifier') == $id) {
2940
                    return $item_temp;
2941
                }
2942
            }
2943
            $subchildren = $item_temp->childNodes;
2944
            if ($subchildren && $subchildren->length > 0) {
2945
                $val = $this->get_scorm_xml_node($subchildren, $id);
2946
                if (is_object($val)) {
2947
                    return $val;
2948
                }
2949
            }
2950
        }
2951
2952
        return false;
2953
    }
2954
2955
    /**
2956
     * Gets the status list for all LP's items.
2957
     *
2958
     * @return array Array of [index] => [item ID => current status]
2959
     */
2960
    public function get_items_status_list()
2961
    {
2962
        if ($this->debug > 0) {
2963
            error_log('In learnpath::get_items_status_list()', 0);
2964
        }
2965
        $list = [];
2966
        foreach ($this->ordered_items as $item_id) {
2967
            $list[] = [
2968
                $item_id => $this->items[$item_id]->get_status(),
2969
            ];
2970
        }
2971
2972
        return $list;
2973
    }
2974
2975
    /**
2976
     * Return the number of interactions for the given learnpath Item View ID.
2977
     * This method can be used as static.
2978
     *
2979
     * @param int $lp_iv_id  Item View ID
2980
     * @param int $course_id course id
2981
     *
2982
     * @return int
2983
     */
2984
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2985
    {
2986
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2987
        $lp_iv_id = (int) $lp_iv_id;
2988
        $course_id = (int) $course_id;
2989
2990
        $sql = "SELECT count(*) FROM $table
2991
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2992
        $res = Database::query($sql);
2993
        $num = 0;
2994
        if (Database::num_rows($res)) {
2995
            $row = Database::fetch_array($res);
2996
            $num = $row[0];
2997
        }
2998
2999
        return $num;
3000
    }
3001
3002
    /**
3003
     * Return the interactions as an array for the given lp_iv_id.
3004
     * This method can be used as static.
3005
     *
3006
     * @param int $lp_iv_id Learnpath Item View ID
3007
     *
3008
     * @return array
3009
     *
3010
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
3011
     */
3012
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
3013
    {
3014
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3015
        $list = [];
3016
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3017
        $lp_iv_id = (int) $lp_iv_id;
3018
3019
        if (empty($lp_iv_id) || empty($course_id)) {
3020
            return [];
3021
        }
3022
3023
        $sql = "SELECT * FROM $table
3024
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
3025
                ORDER BY order_id ASC";
3026
        $res = Database::query($sql);
3027
        $num = Database::num_rows($res);
3028
        if ($num > 0) {
3029
            $list[] = [
3030
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3031
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3032
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3033
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3034
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3035
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3036
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3037
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3038
                'student_response_formatted' => '',
3039
            ];
3040
            while ($row = Database::fetch_array($res)) {
3041
                $studentResponseFormatted = urldecode($row['student_response']);
3042
                $content_student_response = explode('__|', $studentResponseFormatted);
3043
                if (count($content_student_response) > 0) {
3044
                    if (count($content_student_response) >= 3) {
3045
                        // Pop the element off the end of array.
3046
                        array_pop($content_student_response);
3047
                    }
3048
                    $studentResponseFormatted = implode(',', $content_student_response);
3049
                }
3050
3051
                $list[] = [
3052
                    'order_id' => $row['order_id'] + 1,
3053
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3054
                    'type' => $row['interaction_type'],
3055
                    'time' => $row['completion_time'],
3056
                    'correct_responses' => '', // Hide correct responses from students.
3057
                    'student_response' => $row['student_response'],
3058
                    'result' => $row['result'],
3059
                    'latency' => $row['latency'],
3060
                    'student_response_formatted' => $studentResponseFormatted,
3061
                ];
3062
            }
3063
        }
3064
3065
        return $list;
3066
    }
3067
3068
    /**
3069
     * Return the number of objectives for the given learnpath Item View ID.
3070
     * This method can be used as static.
3071
     *
3072
     * @param int $lp_iv_id  Item View ID
3073
     * @param int $course_id Course ID
3074
     *
3075
     * @return int Number of objectives
3076
     */
3077
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3078
    {
3079
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3080
        $course_id = (int) $course_id;
3081
        $lp_iv_id = (int) $lp_iv_id;
3082
        $sql = "SELECT count(*) FROM $table
3083
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3084
        //@todo seems that this always returns 0
3085
        $res = Database::query($sql);
3086
        $num = 0;
3087
        if (Database::num_rows($res)) {
3088
            $row = Database::fetch_array($res);
3089
            $num = $row[0];
3090
        }
3091
3092
        return $num;
3093
    }
3094
3095
    /**
3096
     * Return the objectives as an array for the given lp_iv_id.
3097
     * This method can be used as static.
3098
     *
3099
     * @param int $lpItemViewId Learnpath Item View ID
3100
     * @param int $course_id
3101
     *
3102
     * @return array
3103
     *
3104
     * @todo    Translate labels
3105
     */
3106
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3107
    {
3108
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3109
        $lpItemViewId = (int) $lpItemViewId;
3110
3111
        if (empty($course_id) || empty($lpItemViewId)) {
3112
            return [];
3113
        }
3114
3115
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3116
        $sql = "SELECT * FROM $table
3117
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3118
                ORDER BY order_id ASC";
3119
        $res = Database::query($sql);
3120
        $num = Database::num_rows($res);
3121
        $list = [];
3122
        if ($num > 0) {
3123
            $list[] = [
3124
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3125
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3126
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3127
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3128
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3129
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3130
            ];
3131
            while ($row = Database::fetch_array($res)) {
3132
                $list[] = [
3133
                    'order_id' => $row['order_id'] + 1,
3134
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3135
                    'score_raw' => $row['score_raw'],
3136
                    'score_max' => $row['score_max'],
3137
                    'score_min' => $row['score_min'],
3138
                    'status' => $row['status'],
3139
                ];
3140
            }
3141
        }
3142
3143
        return $list;
3144
    }
3145
3146
    /**
3147
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3148
     * used by get_html_toc() to be ready to display.
3149
     *
3150
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3151
     */
3152
    public function get_toc()
3153
    {
3154
        if ($this->debug > 0) {
3155
            error_log('learnpath::get_toc()', 0);
3156
        }
3157
        $toc = [];
3158
        foreach ($this->ordered_items as $item_id) {
3159
            if ($this->debug > 2) {
3160
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3161
            }
3162
            // TODO: Change this link generation and use new function instead.
3163
            $toc[] = [
3164
                'id' => $item_id,
3165
                'title' => $this->items[$item_id]->get_title(),
3166
                'status' => $this->items[$item_id]->get_status(),
3167
                'level' => $this->items[$item_id]->get_level(),
3168
                'type' => $this->items[$item_id]->get_type(),
3169
                'description' => $this->items[$item_id]->get_description(),
3170
                'path' => $this->items[$item_id]->get_path(),
3171
                'parent' => $this->items[$item_id]->get_parent(),
3172
            ];
3173
        }
3174
        if ($this->debug > 2) {
3175
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3176
        }
3177
3178
        return $toc;
3179
    }
3180
3181
    /**
3182
     * Generate and return the table of contents for this learnpath. The JS
3183
     * table returned is used inside of scorm_api.php.
3184
     *
3185
     * @param string $varname
3186
     *
3187
     * @return string A JS array variable construction
3188
     */
3189
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3190
    {
3191
        if ($this->debug > 0) {
3192
            error_log('In learnpath::get_items_details_as_js()', 0);
3193
        }
3194
        $toc = $varname.' = new Array();';
3195
        foreach ($this->ordered_items as $item_id) {
3196
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3197
        }
3198
        if ($this->debug > 2) {
3199
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3200
        }
3201
3202
        return $toc;
3203
    }
3204
3205
    /**
3206
     * Gets the learning path type.
3207
     *
3208
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3209
     *
3210
     * @return mixed Type ID or name, depending on the parameter
3211
     */
3212
    public function get_type($get_name = false)
3213
    {
3214
        $res = false;
3215
        if ($this->debug > 0) {
3216
            error_log('In learnpath::get_type()', 0);
3217
        }
3218
        if (!empty($this->type) && (!$get_name)) {
3219
            $res = $this->type;
3220
        }
3221
        if ($this->debug > 2) {
3222
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3223
        }
3224
3225
        return $res;
3226
    }
3227
3228
    /**
3229
     * Gets the learning path type as static method.
3230
     *
3231
     * @param int $lp_id
3232
     *
3233
     * @return mixed Type ID or name, depending on the parameter
3234
     */
3235
    public static function get_type_static($lp_id = 0)
3236
    {
3237
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3238
        $lp_id = (int) $lp_id;
3239
        $sql = "SELECT lp_type FROM $tbl_lp
3240
                WHERE iid = $lp_id";
3241
        $res = Database::query($sql);
3242
        if ($res === false) {
3243
            return null;
3244
        }
3245
        if (Database::num_rows($res) <= 0) {
3246
            return null;
3247
        }
3248
        $row = Database::fetch_array($res);
3249
3250
        return $row['lp_type'];
3251
    }
3252
3253
    /**
3254
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3255
     * This method can be used as abstract and is recursive.
3256
     *
3257
     * @param int $lp        Learnpath ID
3258
     * @param int $parent    Parent ID of the items to look for
3259
     * @param int $course_id
3260
     *
3261
     * @return array Ordered list of item IDs (empty array on error)
3262
     */
3263
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3264
    {
3265
        if (empty($course_id)) {
3266
            $course_id = api_get_course_int_id();
3267
        } else {
3268
            $course_id = (int) $course_id;
3269
        }
3270
        $list = [];
3271
3272
        if (empty($lp)) {
3273
            return $list;
3274
        }
3275
3276
        $lp = (int) $lp;
3277
        $parent = (int) $parent;
3278
3279
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3280
        $sql = "SELECT iid FROM $tbl_lp_item
3281
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3282
                ORDER BY display_order";
3283
3284
        $res = Database::query($sql);
3285
        while ($row = Database::fetch_array($res)) {
3286
            $sublist = self::get_flat_ordered_items_list(
3287
                $lp,
3288
                $row['iid'],
3289
                $course_id
3290
            );
3291
            $list[] = $row['iid'];
3292
            foreach ($sublist as $item) {
3293
                $list[] = $item;
3294
            }
3295
        }
3296
3297
        return $list;
3298
    }
3299
3300
    /**
3301
     * @return array
3302
     */
3303
    public static function getChapterTypes()
3304
    {
3305
        return [
3306
            'dir',
3307
        ];
3308
    }
3309
3310
    /**
3311
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3312
     *
3313
     * @param $tree
3314
     *
3315
     * @return array HTML TOC ready to display
3316
     */
3317
    public function getParentToc($tree)
3318
    {
3319
        if ($this->debug > 0) {
3320
            error_log('In learnpath::get_html_toc()', 0);
3321
        }
3322
        if (empty($tree)) {
3323
            $tree = $this->get_toc();
3324
        }
3325
        $dirTypes = self::getChapterTypes();
3326
        $myCurrentId = $this->get_current_item_id();
3327
        $listParent = [];
3328
        $listChildren = [];
3329
        $listNotParent = [];
3330
        $list = [];
3331
        foreach ($tree as $subtree) {
3332
            if (in_array($subtree['type'], $dirTypes)) {
3333
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3334
                $subtree['children'] = $listChildren;
3335
                if (!empty($subtree['children'])) {
3336
                    foreach ($subtree['children'] as $subItem) {
3337
                        if ($subItem['id'] == $this->current) {
3338
                            $subtree['parent_current'] = 'in';
3339
                            $subtree['current'] = 'on';
3340
                        }
3341
                    }
3342
                }
3343
                $listParent[] = $subtree;
3344
            }
3345
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3346
                $classStatus = [
3347
                    'not attempted' => 'scorm_not_attempted',
3348
                    'incomplete' => 'scorm_not_attempted',
3349
                    'failed' => 'scorm_failed',
3350
                    'completed' => 'scorm_completed',
3351
                    'passed' => 'scorm_completed',
3352
                    'succeeded' => 'scorm_completed',
3353
                    'browsed' => 'scorm_completed',
3354
                ];
3355
3356
                if (isset($classStatus[$subtree['status']])) {
3357
                    $cssStatus = $classStatus[$subtree['status']];
3358
                }
3359
3360
                $title = Security::remove_XSS($subtree['title']);
3361
                unset($subtree['title']);
3362
3363
                if (empty($title)) {
3364
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3365
                }
3366
                $classStyle = null;
3367
                if ($subtree['id'] == $this->current) {
3368
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3369
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3370
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3371
                }
3372
                $subtree['title'] = $title;
3373
                $subtree['class'] = $classStyle.' '.$cssStatus;
3374
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3375
                $subtree['current_id'] = $myCurrentId;
3376
                $listNotParent[] = $subtree;
3377
            }
3378
        }
3379
3380
        $list['are_parents'] = $listParent;
3381
        $list['not_parents'] = $listNotParent;
3382
3383
        return $list;
3384
    }
3385
3386
    /**
3387
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3388
     *
3389
     * @param array $tree
3390
     * @param int   $id
3391
     * @param bool  $parent
3392
     *
3393
     * @return array HTML TOC ready to display
3394
     */
3395
    public function getChildrenToc($tree, $id, $parent = true)
3396
    {
3397
        if ($this->debug > 0) {
3398
            error_log('In learnpath::get_html_toc()', 0);
3399
        }
3400
        if (empty($tree)) {
3401
            $tree = $this->get_toc();
3402
        }
3403
3404
        $dirTypes = self::getChapterTypes();
3405
        $mycurrentitemid = $this->get_current_item_id();
3406
        $list = [];
3407
        $classStatus = [
3408
            'not attempted' => 'scorm_not_attempted',
3409
            'incomplete' => 'scorm_not_attempted',
3410
            'failed' => 'scorm_failed',
3411
            'completed' => 'scorm_completed',
3412
            'passed' => 'scorm_completed',
3413
            'succeeded' => 'scorm_completed',
3414
            'browsed' => 'scorm_completed',
3415
        ];
3416
3417
        foreach ($tree as $subtree) {
3418
            $subtree['tree'] = null;
3419
3420
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3421
                if ($subtree['id'] == $this->current) {
3422
                    $subtree['current'] = 'active';
3423
                } else {
3424
                    $subtree['current'] = null;
3425
                }
3426
                if (isset($classStatus[$subtree['status']])) {
3427
                    $cssStatus = $classStatus[$subtree['status']];
3428
                }
3429
3430
                $title = Security::remove_XSS($subtree['title']);
3431
                unset($subtree['title']);
3432
                if (empty($title)) {
3433
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3434
                }
3435
3436
                $classStyle = null;
3437
                if ($subtree['id'] == $this->current) {
3438
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3439
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3440
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3441
                }
3442
3443
                if (in_array($subtree['type'], $dirTypes)) {
3444
                    $subtree['title'] = stripslashes($title);
3445
                } else {
3446
                    $subtree['title'] = $title;
3447
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3448
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3449
                    $subtree['current_id'] = $mycurrentitemid;
3450
                }
3451
                $list[] = $subtree;
3452
            }
3453
        }
3454
3455
        return $list;
3456
    }
3457
3458
    /**
3459
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3460
     *
3461
     * @param array $toc_list
3462
     *
3463
     * @return array HTML TOC ready to display
3464
     */
3465
    public function getListArrayToc($toc_list = [])
3466
    {
3467
        if ($this->debug > 0) {
3468
            error_log('In learnpath::get_html_toc()', 0);
3469
        }
3470
        if (empty($toc_list)) {
3471
            $toc_list = $this->get_toc();
3472
        }
3473
        // Temporary variables.
3474
        $mycurrentitemid = $this->get_current_item_id();
3475
        $list = [];
3476
        $arrayList = [];
3477
        $classStatus = [
3478
            'not attempted' => 'scorm_not_attempted',
3479
            'incomplete' => 'scorm_not_attempted',
3480
            'failed' => 'scorm_failed',
3481
            'completed' => 'scorm_completed',
3482
            'passed' => 'scorm_completed',
3483
            'succeeded' => 'scorm_completed',
3484
            'browsed' => 'scorm_completed',
3485
        ];
3486
3487
        foreach ($toc_list as $item) {
3488
            $list['id'] = $item['id'];
3489
            $list['status'] = $item['status'];
3490
            $cssStatus = null;
3491
3492
            if (isset($classStatus[$item['status']])) {
3493
                $cssStatus = $classStatus[$item['status']];
3494
            }
3495
3496
            $classStyle = ' ';
3497
            $dirTypes = self::getChapterTypes();
3498
3499
            if (in_array($item['type'], $dirTypes)) {
3500
                $classStyle = 'scorm_item_section ';
3501
            }
3502
            if ($item['id'] == $this->current) {
3503
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3504
            } elseif (!in_array($item['type'], $dirTypes)) {
3505
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3506
            }
3507
            $title = $item['title'];
3508
            if (empty($title)) {
3509
                $title = self::rl_get_resource_name(
3510
                    api_get_course_id(),
3511
                    $this->get_id(),
3512
                    $item['id']
3513
                );
3514
            }
3515
            $title = Security::remove_XSS($item['title']);
3516
3517
            if (empty($item['description'])) {
3518
                $list['description'] = $title;
3519
            } else {
3520
                $list['description'] = $item['description'];
3521
            }
3522
3523
            $list['class'] = $classStyle.' '.$cssStatus;
3524
            $list['level'] = $item['level'];
3525
            $list['type'] = $item['type'];
3526
3527
            if (in_array($item['type'], $dirTypes)) {
3528
                $list['css_level'] = 'level_'.$item['level'];
3529
            } else {
3530
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3531
            }
3532
3533
            if (in_array($item['type'], $dirTypes)) {
3534
                $list['title'] = stripslashes($title);
3535
            } else {
3536
                $list['title'] = stripslashes($title);
3537
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3538
                $list['current_id'] = $mycurrentitemid;
3539
            }
3540
            $arrayList[] = $list;
3541
        }
3542
3543
        return $arrayList;
3544
    }
3545
3546
    /**
3547
     * Returns an HTML-formatted string ready to display with teacher buttons
3548
     * in LP view menu.
3549
     *
3550
     * @return string HTML TOC ready to display
3551
     */
3552
    public function get_teacher_toc_buttons()
3553
    {
3554
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3555
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3556
        $html = '';
3557
        if ($isAllow && $hideIcons == false) {
3558
            if ($this->get_lp_session_id() == api_get_session_id()) {
3559
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3560
                $html .= '<div class="btn-group">';
3561
                $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'>".
3562
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3563
                $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'>".
3564
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3565
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3566
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3567
                $html .= '</div>';
3568
                $html .= '</div>';
3569
            }
3570
        }
3571
3572
        return $html;
3573
    }
3574
3575
    /**
3576
     * Gets the learnpath maker name - generally the editor's name.
3577
     *
3578
     * @return string Learnpath maker name
3579
     */
3580
    public function get_maker()
3581
    {
3582
        if ($this->debug > 0) {
3583
            error_log('In learnpath::get_maker()', 0);
3584
        }
3585
        if (!empty($this->maker)) {
3586
            return $this->maker;
3587
        } else {
3588
            return '';
3589
        }
3590
    }
3591
3592
    /**
3593
     * Gets the learnpath name/title.
3594
     *
3595
     * @return string Learnpath name/title
3596
     */
3597
    public function get_name()
3598
    {
3599
        if ($this->debug > 0) {
3600
            error_log('In learnpath::get_name()', 0);
3601
        }
3602
        if (!empty($this->name)) {
3603
            return $this->name;
3604
        } else {
3605
            return 'N/A';
3606
        }
3607
    }
3608
3609
    /**
3610
     * Gets a link to the resource from the present location, depending on item ID.
3611
     *
3612
     * @param string $type         Type of link expected
3613
     * @param int    $item_id      Learnpath item ID
3614
     * @param bool   $provided_toc
3615
     *
3616
     * @return string $provided_toc Link to the lp_item resource
3617
     */
3618
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3619
    {
3620
        $course_id = $this->get_course_int_id();
3621
        if ($this->debug > 0) {
3622
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3623
        }
3624
        if (empty($item_id)) {
3625
            if ($this->debug > 2) {
3626
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3627
                error_log('using current: '.$this->get_current_item_id(), 0);
3628
            }
3629
            $item_id = $this->get_current_item_id();
3630
3631
            if (empty($item_id)) {
3632
                if ($this->debug > 2) {
3633
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3634
                }
3635
                //still empty, this means there was no item_id given and we are not in an object context or
3636
                //the object property is empty, return empty link
3637
                $this->first();
3638
3639
                return '';
3640
            }
3641
        }
3642
3643
        $file = '';
3644
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3645
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3646
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3647
        $item_id = (int) $item_id;
3648
3649
        $sql = "SELECT
3650
                    l.lp_type as ltype,
3651
                    l.path as lpath,
3652
                    li.item_type as litype,
3653
                    li.path as lipath,
3654
                    li.parameters as liparams
3655
        		FROM $lp_table l
3656
                INNER JOIN $lp_item_table li
3657
                ON (li.lp_id = l.iid)
3658
        		WHERE 
3659
        		    li.iid = $item_id 
3660
        		";
3661
        if ($this->debug > 2) {
3662
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3663
        }
3664
        $res = Database::query($sql);
3665
        if (Database::num_rows($res) > 0) {
3666
            $row = Database::fetch_array($res);
3667
            $lp_type = $row['ltype'];
3668
            $lp_path = $row['lpath'];
3669
            $lp_item_type = $row['litype'];
3670
            $lp_item_path = $row['lipath'];
3671
            $lp_item_params = $row['liparams'];
3672
3673
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3674
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3675
            }
3676
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3677
            if ($type === 'http') {
3678
                //web path
3679
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3680
            } else {
3681
                $course_path = $sys_course_path; //system path
3682
            }
3683
3684
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3685
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3686
            if (in_array(
3687
                $lp_item_type,
3688
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3689
            )
3690
            ) {
3691
                $lp_type = 1;
3692
            }
3693
3694
            if ($this->debug > 2) {
3695
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3696
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3697
            }
3698
3699
            // Now go through the specific cases to get the end of the path
3700
            // @todo Use constants instead of int values.
3701
            switch ($lp_type) {
3702
                case 1:
3703
                    $file = self::rl_get_resource_link_for_learnpath(
3704
                        $course_id,
3705
                        $this->get_id(),
3706
                        $item_id,
3707
                        $this->get_view_id()
3708
                    );
3709
                    if ($this->debug > 0) {
3710
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3711
                    }
3712
                    switch ($lp_item_type) {
3713
                        case 'document':
3714
                            // Shows a button to download the file instead of just downloading the file directly.
3715
                            $documentPathInfo = pathinfo($file);
3716
                            if (isset($documentPathInfo['extension'])) {
3717
                                $parsed = parse_url($documentPathInfo['extension']);
3718
                                if (isset($parsed['path'])) {
3719
                                    $extension = $parsed['path'];
3720
                                    $extensionsToDownload = [
3721
                                        'zip',
3722
                                        'ppt',
3723
                                        'pptx',
3724
                                        'ods',
3725
                                        'xlsx',
3726
                                        'xls',
3727
                                        'csv',
3728
                                        'doc',
3729
                                        'docx',
3730
                                        'dot',
3731
                                    ];
3732
3733
                                    if (in_array($extension, $extensionsToDownload)) {
3734
                                        $file = api_get_path(WEB_CODE_PATH).
3735
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3736
                                    }
3737
                                }
3738
                            }
3739
                            break;
3740
                        case 'dir':
3741
                            $file = 'lp_content.php?type=dir';
3742
                            break;
3743
                        case 'link':
3744
                            if (Link::is_youtube_link($file)) {
3745
                                $src = Link::get_youtube_video_id($file);
3746
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3747
                            } elseif (Link::isVimeoLink($file)) {
3748
                                $src = Link::getVimeoLinkId($file);
3749
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3750
                            } else {
3751
                                // If the current site is HTTPS and the link is
3752
                                // HTTP, browsers will refuse opening the link
3753
                                $urlId = api_get_current_access_url_id();
3754
                                $url = api_get_access_url($urlId, false);
3755
                                $protocol = substr($url['url'], 0, 5);
3756
                                if ($protocol === 'https') {
3757
                                    $linkProtocol = substr($file, 0, 5);
3758
                                    if ($linkProtocol === 'http:') {
3759
                                        //this is the special intervention case
3760
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3761
                                    }
3762
                                }
3763
                            }
3764
                            break;
3765
                        case 'quiz':
3766
                            // Check how much attempts of a exercise exits in lp
3767
                            $lp_item_id = $this->get_current_item_id();
3768
                            $lp_view_id = $this->get_view_id();
3769
3770
                            $prevent_reinit = null;
3771
                            if (isset($this->items[$this->current])) {
3772
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3773
                            }
3774
3775
                            if (empty($provided_toc)) {
3776
                                if ($this->debug > 0) {
3777
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3778
                                }
3779
                                $list = $this->get_toc();
3780
                            } else {
3781
                                if ($this->debug > 0) {
3782
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3783
                                }
3784
                                $list = $provided_toc;
3785
                            }
3786
3787
                            $type_quiz = false;
3788
                            foreach ($list as $toc) {
3789
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3790
                                    $type_quiz = true;
3791
                                }
3792
                            }
3793
3794
                            if ($type_quiz) {
3795
                                $lp_item_id = (int) $lp_item_id;
3796
                                $lp_view_id = (int) $lp_view_id;
3797
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3798
                                        WHERE
3799
                                            c_id = $course_id AND
3800
                                            lp_item_id='".$lp_item_id."' AND
3801
                                            lp_view_id ='".$lp_view_id."' AND
3802
                                            status='completed'";
3803
                                $result = Database::query($sql);
3804
                                $row_count = Database:: fetch_row($result);
3805
                                $count_item_view = (int) $row_count[0];
3806
                                $not_multiple_attempt = 0;
3807
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3808
                                    $not_multiple_attempt = 1;
3809
                                }
3810
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3811
                            }
3812
                            break;
3813
                    }
3814
3815
                    $tmp_array = explode('/', $file);
3816
                    $document_name = $tmp_array[count($tmp_array) - 1];
3817
                    if (strpos($document_name, '_DELETED_')) {
3818
                        $file = 'blank.php?error=document_deleted';
3819
                    }
3820
                    break;
3821
                case 2:
3822
                    if ($this->debug > 2) {
3823
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3824
                    }
3825
3826
                    if ($lp_item_type != 'dir') {
3827
                        // Quite complex here:
3828
                        // We want to make sure 'http://' (and similar) links can
3829
                        // be loaded as is (withouth the Chamilo path in front) but
3830
                        // some contents use this form: resource.htm?resource=http://blablabla
3831
                        // which means we have to find a protocol at the path's start, otherwise
3832
                        // it should not be considered as an external URL.
3833
                        // if ($this->prerequisites_match($item_id)) {
3834
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3835
                            if ($this->debug > 2) {
3836
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3837
                            }
3838
                            // Distant url, return as is.
3839
                            $file = $lp_item_path;
3840
                        } else {
3841
                            if ($this->debug > 2) {
3842
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3843
                            }
3844
                            // Prevent getting untranslatable urls.
3845
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3846
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3847
                            // Prepare the path.
3848
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3849
                            // TODO: Fix this for urls with protocol header.
3850
                            $file = str_replace('//', '/', $file);
3851
                            $file = str_replace(':/', '://', $file);
3852
                            if (substr($lp_path, -1) == '/') {
3853
                                $lp_path = substr($lp_path, 0, -1);
3854
                            }
3855
3856
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3857
                                // if file not found.
3858
                                $decoded = html_entity_decode($lp_item_path);
3859
                                list($decoded) = explode('?', $decoded);
3860
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3861
                                    $file = self::rl_get_resource_link_for_learnpath(
3862
                                        $course_id,
3863
                                        $this->get_id(),
3864
                                        $item_id,
3865
                                        $this->get_view_id()
3866
                                    );
3867
                                    if (empty($file)) {
3868
                                        $file = 'blank.php?error=document_not_found';
3869
                                    } else {
3870
                                        $tmp_array = explode('/', $file);
3871
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3872
                                        if (strpos($document_name, '_DELETED_')) {
3873
                                            $file = 'blank.php?error=document_deleted';
3874
                                        } else {
3875
                                            $file = 'blank.php?error=document_not_found';
3876
                                        }
3877
                                    }
3878
                                } else {
3879
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3880
                                }
3881
                            }
3882
                        }
3883
3884
                        // We want to use parameters if they were defined in the imsmanifest
3885
                        if (strpos($file, 'blank.php') === false) {
3886
                            $lp_item_params = ltrim($lp_item_params, '?');
3887
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3888
                        }
3889
                    } else {
3890
                        $file = 'lp_content.php?type=dir';
3891
                    }
3892
                    break;
3893
                case 3:
3894
                    if ($this->debug > 2) {
3895
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3896
                    }
3897
                    // Formatting AICC HACP append URL.
3898
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3899
                    if (!empty($lp_item_params)) {
3900
                        $aicc_append .= $lp_item_params.'&';
3901
                    }
3902
                    if ($lp_item_type != 'dir') {
3903
                        // Quite complex here:
3904
                        // We want to make sure 'http://' (and similar) links can
3905
                        // be loaded as is (withouth the Chamilo path in front) but
3906
                        // some contents use this form: resource.htm?resource=http://blablabla
3907
                        // which means we have to find a protocol at the path's start, otherwise
3908
                        // it should not be considered as an external URL.
3909
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3910
                            if ($this->debug > 2) {
3911
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3912
                            }
3913
                            // Distant url, return as is.
3914
                            $file = $lp_item_path;
3915
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3916
                            /*
3917
                            if (stristr($file,'<servername>') !== false) {
3918
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3919
                            }
3920
                            */
3921
                            if (stripos($file, '<servername>') !== false) {
3922
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3923
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3924
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3925
                            }
3926
3927
                            $file .= $aicc_append;
3928
                        } else {
3929
                            if ($this->debug > 2) {
3930
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3931
                            }
3932
                            // Prevent getting untranslatable urls.
3933
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3934
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3935
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3936
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3937
                            // TODO: Fix this for urls with protocol header.
3938
                            $file = str_replace('//', '/', $file);
3939
                            $file = str_replace(':/', '://', $file);
3940
                            $file .= $aicc_append;
3941
                        }
3942
                    } else {
3943
                        $file = 'lp_content.php?type=dir';
3944
                    }
3945
                    break;
3946
                case 4:
3947
                    break;
3948
                default:
3949
                    break;
3950
            }
3951
            // Replace &amp; by & because &amp; will break URL with params
3952
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3953
        }
3954
        if ($this->debug > 2) {
3955
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3956
        }
3957
3958
        return $file;
3959
    }
3960
3961
    /**
3962
     * Gets the latest usable view or generate a new one.
3963
     *
3964
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3965
     *
3966
     * @return int DB lp_view id
3967
     */
3968
    public function get_view($attempt_num = 0)
3969
    {
3970
        if ($this->debug > 0) {
3971
            error_log('In learnpath::get_view()', 0);
3972
        }
3973
        $search = '';
3974
        // Use $attempt_num to enable multi-views management (disabled so far).
3975
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3976
            $search = 'AND view_count = '.$attempt_num;
3977
        }
3978
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3979
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3980
3981
        $course_id = api_get_course_int_id();
3982
        $sessionId = api_get_session_id();
3983
3984
        $sql = "SELECT iid, view_count FROM $lp_view_table
3985
        		WHERE
3986
        		    c_id = $course_id AND
3987
        		    lp_id = ".$this->get_id()." AND
3988
        		    user_id = ".$this->get_user_id()." AND
3989
        		    session_id = $sessionId
3990
        		    $search
3991
                ORDER BY view_count DESC";
3992
        $res = Database::query($sql);
3993
        if (Database::num_rows($res) > 0) {
3994
            $row = Database::fetch_array($res);
3995
            $this->lp_view_id = $row['iid'];
3996
        } elseif (!api_is_invitee()) {
3997
            // There is no database record, create one.
3998
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3999
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
4000
            Database::query($sql);
4001
            $id = Database::insert_id();
4002
            $this->lp_view_id = $id;
4003
4004
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
4005
            Database::query($sql);
4006
        }
4007
4008
        return $this->lp_view_id;
4009
    }
4010
4011
    /**
4012
     * Gets the current view id.
4013
     *
4014
     * @return int View ID (from lp_view)
4015
     */
4016
    public function get_view_id()
4017
    {
4018
        if ($this->debug > 0) {
4019
            error_log('In learnpath::get_view_id()', 0);
4020
        }
4021
        if (!empty($this->lp_view_id)) {
4022
            return $this->lp_view_id;
4023
        } else {
4024
            return 0;
4025
        }
4026
    }
4027
4028
    /**
4029
     * Gets the update queue.
4030
     *
4031
     * @return array Array containing IDs of items to be updated by JavaScript
4032
     */
4033
    public function get_update_queue()
4034
    {
4035
        if ($this->debug > 0) {
4036
            error_log('In learnpath::get_update_queue()', 0);
4037
        }
4038
4039
        return $this->update_queue;
4040
    }
4041
4042
    /**
4043
     * Gets the user ID.
4044
     *
4045
     * @return int User ID
4046
     */
4047
    public function get_user_id()
4048
    {
4049
        if ($this->debug > 0) {
4050
            error_log('In learnpath::get_user_id()', 0);
4051
        }
4052
        if (!empty($this->user_id)) {
4053
            return $this->user_id;
4054
        } else {
4055
            return false;
4056
        }
4057
    }
4058
4059
    /**
4060
     * Checks if any of the items has an audio element attached.
4061
     *
4062
     * @return bool True or false
4063
     */
4064
    public function has_audio()
4065
    {
4066
        if ($this->debug > 1) {
4067
            error_log('In learnpath::has_audio()', 0);
4068
        }
4069
        $has = false;
4070
        foreach ($this->items as $i => $item) {
4071
            if (!empty($this->items[$i]->audio)) {
4072
                $has = true;
4073
                break;
4074
            }
4075
        }
4076
4077
        return $has;
4078
    }
4079
4080
    /**
4081
     * Moves an item up and down at its level.
4082
     *
4083
     * @param int    $id        Item to move up and down
4084
     * @param string $direction Direction 'up' or 'down'
4085
     *
4086
     * @return bool|int
4087
     */
4088
    public function move_item($id, $direction)
4089
    {
4090
        $course_id = api_get_course_int_id();
4091
        if ($this->debug > 0) {
4092
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4093
        }
4094
        if (empty($id) || empty($direction)) {
4095
            return false;
4096
        }
4097
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4098
        $sql_sel = "SELECT *
4099
                    FROM $tbl_lp_item
4100
                    WHERE  
4101
                        iid = $id
4102
                    ";
4103
        $res_sel = Database::query($sql_sel);
4104
        // Check if elem exists.
4105
        if (Database::num_rows($res_sel) < 1) {
4106
            return false;
4107
        }
4108
        // Gather data.
4109
        $row = Database::fetch_array($res_sel);
4110
        $previous = $row['previous_item_id'];
4111
        $next = $row['next_item_id'];
4112
        $display = $row['display_order'];
4113
        $parent = $row['parent_item_id'];
4114
        $lp = $row['lp_id'];
4115
        // Update the item (switch with previous/next one).
4116
        switch ($direction) {
4117
            case 'up':
4118
                if ($this->debug > 2) {
4119
                    error_log('Movement up detected', 0);
4120
                }
4121
                if ($display > 1) {
4122
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4123
                                 WHERE iid = $previous";
4124
4125
                    if ($this->debug > 2) {
4126
                        error_log('Selecting previous: '.$sql_sel2, 0);
4127
                    }
4128
                    $res_sel2 = Database::query($sql_sel2);
4129
                    if (Database::num_rows($res_sel2) < 1) {
4130
                        $previous_previous = 0;
4131
                    }
4132
                    // Gather data.
4133
                    $row2 = Database::fetch_array($res_sel2);
4134
                    $previous_previous = $row2['previous_item_id'];
4135
                    // Update previous_previous item (switch "next" with current).
4136
                    if ($previous_previous != 0) {
4137
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4138
                                        next_item_id = $id
4139
                                    WHERE iid = $previous_previous";
4140
                        if ($this->debug > 2) {
4141
                            error_log($sql_upd2, 0);
4142
                        }
4143
                        Database::query($sql_upd2);
4144
                    }
4145
                    // Update previous item (switch with current).
4146
                    if ($previous != 0) {
4147
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4148
                                    next_item_id = $next,
4149
                                    previous_item_id = $id,
4150
                                    display_order = display_order +1
4151
                                    WHERE iid = $previous";
4152
                        if ($this->debug > 2) {
4153
                            error_log($sql_upd2, 0);
4154
                        }
4155
                        Database::query($sql_upd2);
4156
                    }
4157
4158
                    // Update current item (switch with previous).
4159
                    if ($id != 0) {
4160
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4161
                                        next_item_id = $previous,
4162
                                        previous_item_id = $previous_previous,
4163
                                        display_order = display_order-1
4164
                                    WHERE c_id = ".$course_id." AND id = $id";
4165
                        if ($this->debug > 2) {
4166
                            error_log($sql_upd2, 0);
4167
                        }
4168
                        Database::query($sql_upd2);
4169
                    }
4170
                    // Update next item (new previous item).
4171
                    if (!empty($next)) {
4172
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4173
                                     WHERE iid = $next";
4174
                        if ($this->debug > 2) {
4175
                            error_log($sql_upd2, 0);
4176
                        }
4177
                        Database::query($sql_upd2);
4178
                    }
4179
                    $display = $display - 1;
4180
                }
4181
                break;
4182
            case 'down':
4183
                if ($this->debug > 2) {
4184
                    error_log('Movement down detected', 0);
4185
                }
4186
                if ($next != 0) {
4187
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4188
                                 WHERE iid = $next";
4189
                    if ($this->debug > 2) {
4190
                        error_log('Selecting next: '.$sql_sel2, 0);
4191
                    }
4192
                    $res_sel2 = Database::query($sql_sel2);
4193
                    if (Database::num_rows($res_sel2) < 1) {
4194
                        $next_next = 0;
4195
                    }
4196
                    // Gather data.
4197
                    $row2 = Database::fetch_array($res_sel2);
4198
                    $next_next = $row2['next_item_id'];
4199
                    // Update previous item (switch with current).
4200
                    if ($previous != 0) {
4201
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4202
                                     SET next_item_id = $next
4203
                                     WHERE iid = $previous";
4204
                        Database::query($sql_upd2);
4205
                    }
4206
                    // Update current item (switch with previous).
4207
                    if ($id != 0) {
4208
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4209
                                     previous_item_id = $next, 
4210
                                     next_item_id = $next_next, 
4211
                                     display_order = display_order + 1
4212
                                     WHERE iid = $id";
4213
                        Database::query($sql_upd2);
4214
                    }
4215
4216
                    // Update next item (new previous item).
4217
                    if ($next != 0) {
4218
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4219
                                     previous_item_id = $previous, 
4220
                                     next_item_id = $id, 
4221
                                     display_order = display_order-1
4222
                                     WHERE iid = $next";
4223
                        Database::query($sql_upd2);
4224
                    }
4225
4226
                    // Update next_next item (switch "previous" with current).
4227
                    if ($next_next != 0) {
4228
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4229
                                     previous_item_id = $id
4230
                                     WHERE iid = $next_next";
4231
                        Database::query($sql_upd2);
4232
                    }
4233
                    $display = $display + 1;
4234
                }
4235
                break;
4236
            default:
4237
                return false;
4238
        }
4239
4240
        return $display;
4241
    }
4242
4243
    /**
4244
     * Move a LP up (display_order).
4245
     *
4246
     * @param int $lp_id      Learnpath ID
4247
     * @param int $categoryId Category ID
4248
     *
4249
     * @return bool
4250
     */
4251
    public static function move_up($lp_id, $categoryId = 0)
4252
    {
4253
        $courseId = api_get_course_int_id();
4254
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4255
4256
        $categoryCondition = '';
4257
        if (!empty($categoryId)) {
4258
            $categoryId = (int) $categoryId;
4259
            $categoryCondition = " AND category_id = $categoryId";
4260
        }
4261
        $sql = "SELECT * FROM $lp_table
4262
                WHERE c_id = $courseId
4263
                $categoryCondition
4264
                ORDER BY display_order";
4265
        $res = Database::query($sql);
4266
        if ($res === false) {
4267
            return false;
4268
        }
4269
4270
        $lps = [];
4271
        $lp_order = [];
4272
        $num = Database::num_rows($res);
4273
        // First check the order is correct, globally (might be wrong because
4274
        // of versions < 1.8.4)
4275
        if ($num > 0) {
4276
            $i = 1;
4277
            while ($row = Database::fetch_array($res)) {
4278
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4279
                    $sql = "UPDATE $lp_table SET display_order = $i
4280
                            WHERE iid = ".$row['iid'];
4281
                    Database::query($sql);
4282
                }
4283
                $row['display_order'] = $i;
4284
                $lps[$row['iid']] = $row;
4285
                $lp_order[$i] = $row['iid'];
4286
                $i++;
4287
            }
4288
        }
4289
        if ($num > 1) { // If there's only one element, no need to sort.
4290
            $order = $lps[$lp_id]['display_order'];
4291
            if ($order > 1) { // If it's the first element, no need to move up.
4292
                $sql = "UPDATE $lp_table SET display_order = $order
4293
                        WHERE iid = ".$lp_order[$order - 1];
4294
                Database::query($sql);
4295
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4296
                        WHERE iid = $lp_id";
4297
                Database::query($sql);
4298
            }
4299
        }
4300
4301
        return true;
4302
    }
4303
4304
    /**
4305
     * Move a learnpath down (display_order).
4306
     *
4307
     * @param int $lp_id      Learnpath ID
4308
     * @param int $categoryId Category ID
4309
     *
4310
     * @return bool
4311
     */
4312
    public static function move_down($lp_id, $categoryId = 0)
4313
    {
4314
        $courseId = api_get_course_int_id();
4315
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4316
4317
        $categoryCondition = '';
4318
        if (!empty($categoryId)) {
4319
            $categoryId = (int) $categoryId;
4320
            $categoryCondition = " AND category_id = $categoryId";
4321
        }
4322
4323
        $sql = "SELECT * FROM $lp_table
4324
                WHERE c_id = $courseId
4325
                $categoryCondition
4326
                ORDER BY display_order";
4327
        $res = Database::query($sql);
4328
        if ($res === false) {
4329
            return false;
4330
        }
4331
        $lps = [];
4332
        $lp_order = [];
4333
        $num = Database::num_rows($res);
4334
        $max = 0;
4335
        // First check the order is correct, globally (might be wrong because
4336
        // of versions < 1.8.4).
4337
        if ($num > 0) {
4338
            $i = 1;
4339
            while ($row = Database::fetch_array($res)) {
4340
                $max = $i;
4341
                if ($row['display_order'] != $i) {
4342
                    // If we find a gap in the order, we need to fix it.
4343
                    $sql = "UPDATE $lp_table SET display_order = $i
4344
                              WHERE iid = ".$row['iid'];
4345
                    Database::query($sql);
4346
                }
4347
                $row['display_order'] = $i;
4348
                $lps[$row['iid']] = $row;
4349
                $lp_order[$i] = $row['iid'];
4350
                $i++;
4351
            }
4352
        }
4353
        if ($num > 1) { // If there's only one element, no need to sort.
4354
            $order = $lps[$lp_id]['display_order'];
4355
            if ($order < $max) { // If it's the first element, no need to move up.
4356
                $sql = "UPDATE $lp_table SET display_order = $order
4357
                        WHERE iid = ".$lp_order[$order + 1];
4358
                Database::query($sql);
4359
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4360
                        WHERE iid = $lp_id";
4361
                Database::query($sql);
4362
            }
4363
        }
4364
4365
        return true;
4366
    }
4367
4368
    /**
4369
     * Updates learnpath attributes to point to the next element
4370
     * The last part is similar to set_current_item but processing the other way around.
4371
     */
4372
    public function next()
4373
    {
4374
        if ($this->debug > 0) {
4375
            error_log('In learnpath::next()', 0);
4376
        }
4377
        $this->last = $this->get_current_item_id();
4378
        $this->items[$this->last]->save(
4379
            false,
4380
            $this->prerequisites_match($this->last)
4381
        );
4382
        $this->autocomplete_parents($this->last);
4383
        $new_index = $this->get_next_index();
4384
        if ($this->debug > 2) {
4385
            error_log('New index: '.$new_index, 0);
4386
        }
4387
        $this->index = $new_index;
4388
        if ($this->debug > 2) {
4389
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4390
        }
4391
        $this->current = $this->ordered_items[$new_index];
4392
        if ($this->debug > 2) {
4393
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4394
        }
4395
    }
4396
4397
    /**
4398
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4399
     * class, this might be redefined to allow several behaviours depending on the document type.
4400
     *
4401
     * @param int $id Resource ID
4402
     */
4403
    public function open($id)
4404
    {
4405
        if ($this->debug > 0) {
4406
            error_log('In learnpath::open()', 0);
4407
        }
4408
        // TODO:
4409
        // set the current resource attribute to this resource
4410
        // switch on element type (redefine in child class?)
4411
        // set status for this item to "opened"
4412
        // start timer
4413
        // initialise score
4414
        $this->index = 0; //or = the last item seen (see $this->last)
4415
    }
4416
4417
    /**
4418
     * Check that all prerequisites are fulfilled. Returns true and an
4419
     * empty string on success, returns false
4420
     * and the prerequisite string on error.
4421
     * This function is based on the rules for aicc_script language as
4422
     * described in the SCORM 1.2 CAM documentation page 108.
4423
     *
4424
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4425
     *
4426
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4427
     *              string otherwise
4428
     */
4429
    public function prerequisites_match($itemId = null)
4430
    {
4431
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4432
        if ($allow) {
4433
            if (api_is_allowed_to_edit() ||
4434
                api_is_platform_admin(true) ||
4435
                api_is_drh() ||
4436
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4437
            ) {
4438
                return true;
4439
            }
4440
        }
4441
4442
        $debug = $this->debug;
4443
        if ($debug > 0) {
4444
            error_log('In learnpath::prerequisites_match()', 0);
4445
        }
4446
4447
        if (empty($itemId)) {
4448
            $itemId = $this->current;
4449
        }
4450
4451
        $currentItem = $this->getItem($itemId);
4452
4453
        if ($currentItem) {
4454
            if ($this->type == 2) {
4455
                // Getting prereq from scorm
4456
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4457
            } else {
4458
                $prereq_string = $currentItem->get_prereq_string();
4459
            }
4460
4461
            if (empty($prereq_string)) {
4462
                if ($debug > 0) {
4463
                    error_log('Found prereq_string is empty return true');
4464
                }
4465
4466
                return true;
4467
            }
4468
            // Clean spaces.
4469
            $prereq_string = str_replace(' ', '', $prereq_string);
4470
            if ($debug > 0) {
4471
                error_log('Found prereq_string: '.$prereq_string, 0);
4472
            }
4473
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4474
            $result = $currentItem->parse_prereq(
4475
                $prereq_string,
4476
                $this->items,
4477
                $this->refs_list,
4478
                $this->get_user_id()
4479
            );
4480
4481
            if ($result === false) {
4482
                $this->set_error_msg($currentItem->prereq_alert);
4483
            }
4484
        } else {
4485
            $result = true;
4486
            if ($debug > 1) {
4487
                error_log('$this->items['.$itemId.'] was not an object', 0);
4488
            }
4489
        }
4490
4491
        if ($debug > 1) {
4492
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4493
        }
4494
4495
        return $result;
4496
    }
4497
4498
    /**
4499
     * Updates learnpath attributes to point to the previous element
4500
     * The last part is similar to set_current_item but processing the other way around.
4501
     */
4502
    public function previous()
4503
    {
4504
        if ($this->debug > 0) {
4505
            error_log('In learnpath::previous()', 0);
4506
        }
4507
        $this->last = $this->get_current_item_id();
4508
        $this->items[$this->last]->save(
4509
            false,
4510
            $this->prerequisites_match($this->last)
4511
        );
4512
        $this->autocomplete_parents($this->last);
4513
        $new_index = $this->get_previous_index();
4514
        $this->index = $new_index;
4515
        $this->current = $this->ordered_items[$new_index];
4516
    }
4517
4518
    /**
4519
     * Publishes a learnpath. This basically means show or hide the learnpath
4520
     * to normal users.
4521
     * Can be used as abstract.
4522
     *
4523
     * @param int $lp_id          Learnpath ID
4524
     * @param int $set_visibility New visibility
4525
     *
4526
     * @return bool
4527
     */
4528
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4529
    {
4530
        $action = 'visible';
4531
        if ($set_visibility != 1) {
4532
            $action = 'invisible';
4533
            self::toggle_publish($lp_id, 'i');
4534
        }
4535
4536
        return api_item_property_update(
4537
            api_get_course_info(),
4538
            TOOL_LEARNPATH,
4539
            $lp_id,
4540
            $action,
4541
            api_get_user_id()
4542
        );
4543
    }
4544
4545
    /**
4546
     * Publishes a learnpath category.
4547
     * This basically means show or hide the learnpath category to normal users.
4548
     *
4549
     * @param int $id
4550
     * @param int $visibility
4551
     *
4552
     * @throws \Doctrine\ORM\NonUniqueResultException
4553
     * @throws \Doctrine\ORM\ORMException
4554
     * @throws \Doctrine\ORM\OptimisticLockException
4555
     * @throws \Doctrine\ORM\TransactionRequiredException
4556
     *
4557
     * @return bool
4558
     */
4559
    public static function toggleCategoryVisibility($id, $visibility = 1)
4560
    {
4561
        $action = 'visible';
4562
4563
        if ($visibility != 1) {
4564
            $action = 'invisible';
4565
            $list = new LearnpathList(
4566
                api_get_user_id(),
4567
                null,
4568
                null,
4569
                null,
4570
                false,
4571
                $id
4572
            );
4573
4574
            $lpList = $list->get_flat_list();
4575
            foreach ($lpList as $lp) {
4576
                self::toggle_visibility($lp['iid'], 0);
4577
            }
4578
4579
            self::toggleCategoryPublish($id, 0);
4580
        }
4581
4582
        return api_item_property_update(
4583
            api_get_course_info(),
4584
            TOOL_LEARNPATH_CATEGORY,
4585
            $id,
4586
            $action,
4587
            api_get_user_id()
4588
        );
4589
    }
4590
4591
    /**
4592
     * Publishes a learnpath. This basically means show or hide the learnpath
4593
     * on the course homepage
4594
     * Can be used as abstract.
4595
     *
4596
     * @param int    $lp_id          Learnpath id
4597
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4598
     *
4599
     * @return bool
4600
     */
4601
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4602
    {
4603
        $course_id = api_get_course_int_id();
4604
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4605
        $lp_id = (int) $lp_id;
4606
        $sql = "SELECT * FROM $tbl_lp
4607
                WHERE iid = $lp_id";
4608
        $result = Database::query($sql);
4609
        if (Database::num_rows($result)) {
4610
            $row = Database::fetch_array($result);
4611
            $name = Database::escape_string($row['name']);
4612
            if ($set_visibility == 'i') {
4613
                $v = 0;
4614
            }
4615
            if ($set_visibility == 'v') {
4616
                $v = 1;
4617
            }
4618
4619
            $session_id = api_get_session_id();
4620
            $session_condition = api_get_session_condition($session_id);
4621
4622
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4623
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4624
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4625
4626
            $sql = "SELECT * FROM $tbl_tool
4627
                    WHERE
4628
                        c_id = $course_id AND
4629
                        (link = '$link' OR link = '$oldLink') AND
4630
                        image = 'scormbuilder.gif' AND
4631
                        (
4632
                            link LIKE '$link%' OR
4633
                            link LIKE '$oldLink%'
4634
                        )
4635
                        $session_condition
4636
                    ";
4637
4638
            $result = Database::query($sql);
4639
            $num = Database::num_rows($result);
4640
            if ($set_visibility == 'i' && $num > 0) {
4641
                $sql = "DELETE FROM $tbl_tool
4642
                        WHERE 
4643
                            c_id = $course_id AND 
4644
                            (link = '$link' OR link = '$oldLink') AND 
4645
                            image='scormbuilder.gif' 
4646
                            $session_condition";
4647
                Database::query($sql);
4648
            } elseif ($set_visibility == 'v' && $num == 0) {
4649
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4650
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4651
                Database::query($sql);
4652
                $insertId = Database::insert_id();
4653
                if ($insertId) {
4654
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4655
                    Database::query($sql);
4656
                }
4657
            } elseif ($set_visibility == 'v' && $num > 0) {
4658
                $sql = "UPDATE $tbl_tool SET
4659
                            c_id = $course_id,
4660
                            name = '$name',
4661
                            link = '$link',
4662
                            image = 'scormbuilder.gif',
4663
                            visibility = '$v',
4664
                            admin = '0',
4665
                            address = 'pastillegris.gif',
4666
                            added_tool = 0,
4667
                            session_id = $session_id
4668
                        WHERE
4669
                            c_id = ".$course_id." AND
4670
                            (link = '$link' OR link = '$oldLink') AND 
4671
                            image='scormbuilder.gif' 
4672
                            $session_condition
4673
                        ";
4674
                Database::query($sql);
4675
            } else {
4676
                // Parameter and database incompatible, do nothing, exit.
4677
                return false;
4678
            }
4679
        } else {
4680
            return false;
4681
        }
4682
    }
4683
4684
    /**
4685
     * Publishes a learnpath.
4686
     * Show or hide the learnpath category on the course homepage.
4687
     *
4688
     * @param int $id
4689
     * @param int $setVisibility
4690
     *
4691
     * @throws \Doctrine\ORM\NonUniqueResultException
4692
     * @throws \Doctrine\ORM\ORMException
4693
     * @throws \Doctrine\ORM\OptimisticLockException
4694
     * @throws \Doctrine\ORM\TransactionRequiredException
4695
     *
4696
     * @return bool
4697
     */
4698
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4699
    {
4700
        $courseId = api_get_course_int_id();
4701
        $sessionId = api_get_session_id();
4702
        $sessionCondition = api_get_session_condition(
4703
            $sessionId,
4704
            true,
4705
            false,
4706
            't.sessionId'
4707
        );
4708
4709
        $em = Database::getManager();
4710
4711
        /** @var CLpCategory $category */
4712
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4713
4714
        if (!$category) {
4715
            return false;
4716
        }
4717
4718
        if (empty($courseId)) {
4719
            return false;
4720
        }
4721
4722
        $link = self::getCategoryLinkForTool($id);
4723
4724
        /** @var CTool $tool */
4725
        $tool = $em->createQuery("
4726
                SELECT t FROM ChamiloCourseBundle:CTool t
4727
                WHERE
4728
                    t.course = :course AND
4729
                    t.link = :link1 AND
4730
                    t.image LIKE 'lp_category.%' AND
4731
                    t.link LIKE :link2
4732
                    $sessionCondition
4733
            ")
4734
            ->setParameters([
4735
                'course' => $courseId,
4736
                'link1' => $link,
4737
                'link2' => "$link%",
4738
            ])
4739
            ->getOneOrNullResult();
4740
4741
        if ($setVisibility == 0 && $tool) {
4742
            $em->remove($tool);
4743
            $em->flush();
4744
4745
            return true;
4746
        }
4747
4748
        if ($setVisibility == 1 && !$tool) {
4749
            $tool = new CTool();
4750
            $tool
4751
                ->setCategory('authoring')
4752
                ->setCourse(api_get_course_entity($courseId))
4753
                ->setName(strip_tags($category->getName()))
4754
                ->setLink($link)
4755
                ->setImage('lp_category.png')
4756
                ->setVisibility(1)
4757
                ->setAdmin(0)
4758
                ->setAddress('pastillegris.gif')
4759
                ->setAddedTool(0)
4760
                ->setSessionId($sessionId)
4761
                ->setTarget('_self');
4762
4763
            $em->persist($tool);
4764
            $em->flush();
4765
4766
            $tool->setId($tool->getIid());
4767
4768
            $em->persist($tool);
4769
            $em->flush();
4770
4771
            return true;
4772
        }
4773
4774
        if ($setVisibility == 1 && $tool) {
4775
            $tool
4776
                ->setName(strip_tags($category->getName()))
4777
                ->setVisibility(1);
4778
4779
            $em->persist($tool);
4780
            $em->flush();
4781
4782
            return true;
4783
        }
4784
4785
        return false;
4786
    }
4787
4788
    /**
4789
     * Check if the learnpath category is visible for a user.
4790
     *
4791
     * @param CLpCategory $category
4792
     * @param User        $user
4793
     * @param int
4794
     * @param int
4795
     *
4796
     * @return bool
4797
     */
4798
    public static function categoryIsVisibleForStudent(
4799
        CLpCategory $category,
4800
        User $user,
4801
        $courseId = 0,
4802
        $sessionId = 0
4803
    ) {
4804
        $subscriptionSettings = self::getSubscriptionSettings();
4805
4806
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4807
            return true;
4808
        }
4809
4810
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4811
4812
        if ($isAllowedToEdit) {
4813
            return true;
4814
        }
4815
4816
        if (empty($category)) {
4817
            return false;
4818
        }
4819
4820
        $users = $category->getUsers();
4821
4822
        if (empty($users) || !$users->count()) {
4823
            return true;
4824
        }
4825
4826
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4827
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4828
4829
        if ($category->hasUserAdded($user)) {
4830
            return true;
4831
        }
4832
4833
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4834
        if (!empty($groups)) {
4835
            $em = Database::getManager();
4836
4837
            /** @var ItemPropertyRepository $itemRepo */
4838
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4839
4840
            /** @var CourseRepository $courseRepo */
4841
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4842
            $session = null;
4843
            if (!empty($sessionId)) {
4844
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4845
            }
4846
4847
            $course = $courseRepo->find($courseId);
4848
4849
            // Subscribed groups to a LP
4850
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4851
                TOOL_LEARNPATH_CATEGORY,
4852
                $category->getId(),
4853
                $course,
4854
                $session
4855
            );
4856
4857
            if (!empty($subscribedGroupsInLp)) {
4858
                $groups = array_column($groups, 'iid');
4859
                /** @var CItemProperty $item */
4860
                foreach ($subscribedGroupsInLp as $item) {
4861
                    if ($item->getGroup() &&
4862
                        in_array($item->getGroup()->getId(), $groups)
4863
                    ) {
4864
                        return true;
4865
                    }
4866
                }
4867
            }
4868
        }
4869
4870
        return false;
4871
    }
4872
4873
    /**
4874
     * Check if a learnpath category is published as course tool.
4875
     *
4876
     * @param CLpCategory $category
4877
     * @param int         $courseId
4878
     *
4879
     * @return bool
4880
     */
4881
    public static function categoryIsPublished(
4882
        CLpCategory $category,
4883
        $courseId
4884
    ) {
4885
        $link = self::getCategoryLinkForTool($category->getId());
4886
        $em = Database::getManager();
4887
4888
        $tools = $em
4889
            ->createQuery("
4890
                SELECT t FROM ChamiloCourseBundle:CTool t
4891
                WHERE t.course = :course AND 
4892
                    t.name = :name AND
4893
                    t.image LIKE 'lp_category.%' AND
4894
                    t.link LIKE :link
4895
            ")
4896
            ->setParameters([
4897
                'course' => $courseId,
4898
                'name' => strip_tags($category->getName()),
4899
                'link' => "$link%",
4900
            ])
4901
            ->getResult();
4902
4903
        /** @var CTool $tool */
4904
        $tool = current($tools);
4905
4906
        return $tool ? $tool->getVisibility() : false;
4907
    }
4908
4909
    /**
4910
     * Restart the whole learnpath. Return the URL of the first element.
4911
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4912
     * To use a similar method  statically, use the create_new_attempt() method.
4913
     *
4914
     * @return bool
4915
     */
4916
    public function restart()
4917
    {
4918
        if ($this->debug > 0) {
4919
            error_log('In learnpath::restart()', 0);
4920
        }
4921
        // TODO
4922
        // Call autosave method to save the current progress.
4923
        //$this->index = 0;
4924
        if (api_is_invitee()) {
4925
            return false;
4926
        }
4927
        $session_id = api_get_session_id();
4928
        $course_id = api_get_course_int_id();
4929
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4930
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4931
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4932
        if ($this->debug > 2) {
4933
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4934
        }
4935
        Database::query($sql);
4936
        $view_id = Database::insert_id();
4937
4938
        if ($view_id) {
4939
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4940
            Database::query($sql);
4941
            $this->lp_view_id = $view_id;
4942
            $this->attempt = $this->attempt + 1;
4943
        } else {
4944
            $this->error = 'Could not insert into item_view table...';
4945
4946
            return false;
4947
        }
4948
        $this->autocomplete_parents($this->current);
4949
        foreach ($this->items as $index => $dummy) {
4950
            $this->items[$index]->restart();
4951
            $this->items[$index]->set_lp_view($this->lp_view_id);
4952
        }
4953
        $this->first();
4954
4955
        return true;
4956
    }
4957
4958
    /**
4959
     * Saves the current item.
4960
     *
4961
     * @return bool
4962
     */
4963
    public function save_current()
4964
    {
4965
        $debug = $this->debug;
4966
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4967
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4968
        if ($debug) {
4969
            error_log('save_current() saving item '.$this->current, 0);
4970
            error_log(''.print_r($this->items, true), 0);
4971
        }
4972
        if (isset($this->items[$this->current]) &&
4973
            is_object($this->items[$this->current])
4974
        ) {
4975
            if ($debug) {
4976
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4977
            }
4978
4979
            $res = $this->items[$this->current]->save(
4980
                false,
4981
                $this->prerequisites_match($this->current)
4982
            );
4983
            $this->autocomplete_parents($this->current);
4984
            $status = $this->items[$this->current]->get_status();
4985
            $this->update_queue[$this->current] = $status;
4986
4987
            if ($debug) {
4988
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4989
            }
4990
4991
            return $res;
4992
        }
4993
4994
        return false;
4995
    }
4996
4997
    /**
4998
     * Saves the given item.
4999
     *
5000
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
5001
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
5002
     *
5003
     * @return bool
5004
     */
5005
    public function save_item($item_id = null, $from_outside = true)
5006
    {
5007
        $debug = $this->debug;
5008
        if ($debug) {
5009
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
5010
        }
5011
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
5012
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
5013
        if (empty($item_id)) {
5014
            $item_id = (int) $_REQUEST['id'];
5015
        }
5016
5017
        if (empty($item_id)) {
5018
            $item_id = $this->get_current_item_id();
5019
        }
5020
        if (isset($this->items[$item_id]) &&
5021
            is_object($this->items[$item_id])
5022
        ) {
5023
            if ($debug) {
5024
                error_log('Object exists');
5025
            }
5026
5027
            // Saving the item.
5028
            $res = $this->items[$item_id]->save(
5029
                $from_outside,
5030
                $this->prerequisites_match($item_id)
5031
            );
5032
5033
            if ($debug) {
5034
                error_log('update_queue before:');
5035
                error_log(print_r($this->update_queue, 1));
5036
            }
5037
            $this->autocomplete_parents($item_id);
5038
5039
            $status = $this->items[$item_id]->get_status();
5040
            $this->update_queue[$item_id] = $status;
5041
5042
            if ($debug) {
5043
                error_log('get_status(): '.$status);
5044
                error_log('update_queue after:');
5045
                error_log(print_r($this->update_queue, 1));
5046
            }
5047
5048
            return $res;
5049
        }
5050
5051
        return false;
5052
    }
5053
5054
    /**
5055
     * Saves the last item seen's ID only in case.
5056
     */
5057
    public function save_last()
5058
    {
5059
        $course_id = api_get_course_int_id();
5060
        $debug = $this->debug;
5061
        if ($debug) {
5062
            error_log('In learnpath::save_last()', 0);
5063
        }
5064
        $session_condition = api_get_session_condition(
5065
            api_get_session_id(),
5066
            true,
5067
            false
5068
        );
5069
        $table = Database::get_course_table(TABLE_LP_VIEW);
5070
5071
        if (isset($this->current) && !api_is_invitee()) {
5072
            if ($debug) {
5073
                error_log('Saving current item ('.$this->current.') for later review', 0);
5074
            }
5075
            $sql = "UPDATE $table SET
5076
                        last_item = ".intval($this->get_current_item_id())."
5077
                    WHERE
5078
                        c_id = $course_id AND
5079
                        lp_id = ".$this->get_id()." AND
5080
                        user_id = ".$this->get_user_id()." ".$session_condition;
5081
5082
            if ($debug) {
5083
                error_log('Saving last item seen : '.$sql, 0);
5084
            }
5085
            Database::query($sql);
5086
        }
5087
5088
        if (!api_is_invitee()) {
5089
            // Save progress.
5090
            list($progress) = $this->get_progress_bar_text('%');
5091
            if ($progress >= 0 && $progress <= 100) {
5092
                $progress = (int) $progress;
5093
                $sql = "UPDATE $table SET
5094
                            progress = $progress
5095
                        WHERE
5096
                            c_id = $course_id AND
5097
                            lp_id = ".$this->get_id()." AND
5098
                            user_id = ".$this->get_user_id()." ".$session_condition;
5099
                // Ignore errors as some tables might not have the progress field just yet.
5100
                Database::query($sql);
5101
                if ($debug) {
5102
                    error_log($sql);
5103
                }
5104
                $this->progress_db = $progress;
5105
            }
5106
        }
5107
    }
5108
5109
    /**
5110
     * Sets the current item ID (checks if valid and authorized first).
5111
     *
5112
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5113
     */
5114
    public function set_current_item($item_id = null)
5115
    {
5116
        $debug = $this->debug;
5117
        if ($debug) {
5118
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5119
        }
5120
        if (empty($item_id)) {
5121
            if ($debug) {
5122
                error_log('No new current item given, ignore...', 0);
5123
            }
5124
            // Do nothing.
5125
        } else {
5126
            if ($debug) {
5127
                error_log('New current item given is '.$item_id.'...', 0);
5128
            }
5129
            if (is_numeric($item_id)) {
5130
                $item_id = (int) $item_id;
5131
                // TODO: Check in database here.
5132
                $this->last = $this->current;
5133
                $this->current = $item_id;
5134
                // TODO: Update $this->index as well.
5135
                foreach ($this->ordered_items as $index => $item) {
5136
                    if ($item == $this->current) {
5137
                        $this->index = $index;
5138
                        break;
5139
                    }
5140
                }
5141
                if ($debug) {
5142
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5143
                }
5144
            } else {
5145
                if ($debug) {
5146
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5147
                }
5148
            }
5149
        }
5150
    }
5151
5152
    /**
5153
     * Sets the encoding.
5154
     *
5155
     * @param string $enc New encoding
5156
     *
5157
     * @return bool
5158
     *
5159
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5160
     */
5161
    public function set_encoding($enc = 'UTF-8')
5162
    {
5163
        if ($this->debug > 0) {
5164
            error_log('In learnpath::set_encoding()', 0);
5165
        }
5166
5167
        $enc = api_refine_encoding_id($enc);
5168
        if (empty($enc)) {
5169
            $enc = api_get_system_encoding();
5170
        }
5171
        if (api_is_encoding_supported($enc)) {
5172
            $lp = $this->get_id();
5173
            if ($lp != 0) {
5174
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5175
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5176
                        WHERE iid = ".$lp;
5177
                $res = Database::query($sql);
5178
5179
                return $res;
5180
            }
5181
        }
5182
5183
        return false;
5184
    }
5185
5186
    /**
5187
     * Sets the JS lib setting in the database directly.
5188
     * This is the JavaScript library file this lp needs to load on startup.
5189
     *
5190
     * @param string $lib Proximity setting
5191
     *
5192
     * @return bool True on update success. False otherwise.
5193
     */
5194
    public function set_jslib($lib = '')
5195
    {
5196
        if ($this->debug > 0) {
5197
            error_log('In learnpath::set_jslib()', 0);
5198
        }
5199
        $lp = $this->get_id();
5200
5201
        if ($lp != 0) {
5202
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5203
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5204
                    WHERE iid = $lp";
5205
            $res = Database::query($sql);
5206
5207
            return $res;
5208
        } else {
5209
            return false;
5210
        }
5211
    }
5212
5213
    /**
5214
     * Sets the name of the LP maker (publisher) (and save).
5215
     *
5216
     * @param string $name Optional string giving the new content_maker of this learnpath
5217
     *
5218
     * @return bool True
5219
     */
5220
    public function set_maker($name = '')
5221
    {
5222
        if ($this->debug > 0) {
5223
            error_log('In learnpath::set_maker()', 0);
5224
        }
5225
        if (empty($name)) {
5226
            return false;
5227
        }
5228
        $this->maker = $name;
5229
        $table = Database::get_course_table(TABLE_LP_MAIN);
5230
        $lp_id = $this->get_id();
5231
        $sql = "UPDATE $table SET
5232
                content_maker = '".Database::escape_string($this->maker)."'
5233
                WHERE iid = $lp_id";
5234
        if ($this->debug > 2) {
5235
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5236
        }
5237
        Database::query($sql);
5238
5239
        return true;
5240
    }
5241
5242
    /**
5243
     * Sets the name of the current learnpath (and save).
5244
     *
5245
     * @param string $name Optional string giving the new name of this learnpath
5246
     *
5247
     * @return bool True/False
5248
     */
5249
    public function set_name($name = null)
5250
    {
5251
        if ($this->debug > 0) {
5252
            error_log('In learnpath::set_name()', 0);
5253
        }
5254
        if (empty($name)) {
5255
            return false;
5256
        }
5257
        $this->name = Database::escape_string($name);
5258
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5259
        $lp_id = $this->get_id();
5260
        $course_id = $this->course_info['real_id'];
5261
        $sql = "UPDATE $lp_table SET
5262
                name = '".Database::escape_string($this->name)."'
5263
                WHERE iid = $lp_id";
5264
        if ($this->debug > 2) {
5265
            error_log('lp updated with new name : '.$this->name, 0);
5266
        }
5267
        $result = Database::query($sql);
5268
        // If the lp is visible on the homepage, change his name there.
5269
        if (Database::affected_rows($result)) {
5270
            $session_id = api_get_session_id();
5271
            $session_condition = api_get_session_condition($session_id);
5272
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5273
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5274
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5275
            	    WHERE
5276
            	        c_id = $course_id AND
5277
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5278
            Database::query($sql);
5279
5280
            return true;
5281
        } else {
5282
            return false;
5283
        }
5284
    }
5285
5286
    /**
5287
     * Set index specified prefix terms for all items in this path.
5288
     *
5289
     * @param string $terms_string Comma-separated list of terms
5290
     * @param string $prefix       Xapian term prefix
5291
     *
5292
     * @return bool False on error, true otherwise
5293
     */
5294
    public function set_terms_by_prefix($terms_string, $prefix)
5295
    {
5296
        $course_id = api_get_course_int_id();
5297
        if (api_get_setting('search_enabled') !== 'true') {
5298
            return false;
5299
        }
5300
5301
        if (!extension_loaded('xapian')) {
5302
            return false;
5303
        }
5304
5305
        $terms_string = trim($terms_string);
5306
        $terms = explode(',', $terms_string);
5307
        array_walk($terms, 'trim_value');
5308
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5309
5310
        // Don't do anything if no change, verify only at DB, not the search engine.
5311
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5312
            return false;
5313
        }
5314
5315
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5316
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5317
5318
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5319
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5320
        $lp_id = (int) $_POST['lp_id'];
5321
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5322
        $result = Database::query($sql);
5323
        $di = new ChamiloIndexer();
5324
5325
        while ($lp_item = Database::fetch_array($result)) {
5326
            // Get search_did.
5327
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5328
            $sql = 'SELECT * FROM %s
5329
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5330
                    LIMIT 1';
5331
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5332
5333
            //echo $sql; echo '<br>';
5334
            $res = Database::query($sql);
5335
            if (Database::num_rows($res) > 0) {
5336
                $se_ref = Database::fetch_array($res);
5337
                // Compare terms.
5338
                $doc = $di->get_document($se_ref['search_did']);
5339
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5340
                $xterms = [];
5341
                foreach ($xapian_terms as $xapian_term) {
5342
                    $xterms[] = substr($xapian_term['name'], 1);
5343
                }
5344
5345
                $dterms = $terms;
5346
                $missing_terms = array_diff($dterms, $xterms);
5347
                $deprecated_terms = array_diff($xterms, $dterms);
5348
5349
                // Save it to search engine.
5350
                foreach ($missing_terms as $term) {
5351
                    $doc->add_term($prefix.$term, 1);
5352
                }
5353
                foreach ($deprecated_terms as $term) {
5354
                    $doc->remove_term($prefix.$term);
5355
                }
5356
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5357
                $di->getDb()->flush();
5358
            }
5359
        }
5360
5361
        return true;
5362
    }
5363
5364
    /**
5365
     * Sets the theme of the LP (local/remote) (and save).
5366
     *
5367
     * @param string $name Optional string giving the new theme of this learnpath
5368
     *
5369
     * @return bool Returns true if theme name is not empty
5370
     */
5371
    public function set_theme($name = '')
5372
    {
5373
        if ($this->debug > 0) {
5374
            error_log('In learnpath::set_theme()', 0);
5375
        }
5376
        $this->theme = $name;
5377
        $table = Database::get_course_table(TABLE_LP_MAIN);
5378
        $lp_id = $this->get_id();
5379
        $sql = "UPDATE $table 
5380
                SET theme = '".Database::escape_string($this->theme)."'
5381
                WHERE iid = $lp_id";
5382
        if ($this->debug > 2) {
5383
            error_log('lp updated with new theme : '.$this->theme, 0);
5384
        }
5385
        Database::query($sql);
5386
5387
        return true;
5388
    }
5389
5390
    /**
5391
     * Sets the image of an LP (and save).
5392
     *
5393
     * @param string $name Optional string giving the new image of this learnpath
5394
     *
5395
     * @return bool Returns true if theme name is not empty
5396
     */
5397
    public function set_preview_image($name = '')
5398
    {
5399
        if ($this->debug > 0) {
5400
            error_log('In learnpath::set_preview_image()', 0);
5401
        }
5402
5403
        $this->preview_image = $name;
5404
        $table = Database::get_course_table(TABLE_LP_MAIN);
5405
        $lp_id = $this->get_id();
5406
        $sql = "UPDATE $table SET
5407
                preview_image = '".Database::escape_string($this->preview_image)."'
5408
                WHERE iid = $lp_id";
5409
        if ($this->debug > 2) {
5410
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5411
        }
5412
        Database::query($sql);
5413
5414
        return true;
5415
    }
5416
5417
    /**
5418
     * Sets the author of a LP (and save).
5419
     *
5420
     * @param string $name Optional string giving the new author of this learnpath
5421
     *
5422
     * @return bool Returns true if author's name is not empty
5423
     */
5424
    public function set_author($name = '')
5425
    {
5426
        if ($this->debug > 0) {
5427
            error_log('In learnpath::set_author()', 0);
5428
        }
5429
        $this->author = $name;
5430
        $table = Database::get_course_table(TABLE_LP_MAIN);
5431
        $lp_id = $this->get_id();
5432
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5433
                WHERE iid = $lp_id";
5434
        if ($this->debug > 2) {
5435
            error_log('lp updated with new preview author : '.$this->author, 0);
5436
        }
5437
        Database::query($sql);
5438
5439
        return true;
5440
    }
5441
5442
    /**
5443
     * Sets the hide_toc_frame parameter of a LP (and save).
5444
     *
5445
     * @param int $hide 1 if frame is hidden 0 then else
5446
     *
5447
     * @return bool Returns true if author's name is not empty
5448
     */
5449
    public function set_hide_toc_frame($hide)
5450
    {
5451
        if ($this->debug > 0) {
5452
            error_log('In learnpath::set_hide_toc_frame()', 0);
5453
        }
5454
        if (intval($hide) == $hide) {
5455
            $this->hide_toc_frame = $hide;
5456
            $table = Database::get_course_table(TABLE_LP_MAIN);
5457
            $lp_id = $this->get_id();
5458
            $sql = "UPDATE $table SET
5459
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5460
                    WHERE iid = $lp_id";
5461
            if ($this->debug > 2) {
5462
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5463
            }
5464
            Database::query($sql);
5465
5466
            return true;
5467
        } else {
5468
            return false;
5469
        }
5470
    }
5471
5472
    /**
5473
     * Sets the prerequisite of a LP (and save).
5474
     *
5475
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5476
     *
5477
     * @return bool returns true if prerequisite is not empty
5478
     */
5479
    public function set_prerequisite($prerequisite)
5480
    {
5481
        if ($this->debug > 0) {
5482
            error_log('In learnpath::set_prerequisite()', 0);
5483
        }
5484
        $this->prerequisite = (int) $prerequisite;
5485
        $table = Database::get_course_table(TABLE_LP_MAIN);
5486
        $lp_id = $this->get_id();
5487
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5488
                WHERE iid = $lp_id";
5489
        if ($this->debug > 2) {
5490
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5491
        }
5492
        Database::query($sql);
5493
5494
        return true;
5495
    }
5496
5497
    /**
5498
     * Sets the location/proximity of the LP (local/remote) (and save).
5499
     *
5500
     * @param string $name Optional string giving the new location of this learnpath
5501
     *
5502
     * @return bool True on success / False on error
5503
     */
5504
    public function set_proximity($name = '')
5505
    {
5506
        if ($this->debug > 0) {
5507
            error_log('In learnpath::set_proximity()', 0);
5508
        }
5509
        if (empty($name)) {
5510
            return false;
5511
        }
5512
5513
        $this->proximity = $name;
5514
        $table = Database::get_course_table(TABLE_LP_MAIN);
5515
        $lp_id = $this->get_id();
5516
        $sql = "UPDATE $table SET
5517
                    content_local = '".Database::escape_string($name)."'
5518
                WHERE iid = $lp_id";
5519
        if ($this->debug > 2) {
5520
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5521
        }
5522
        Database::query($sql);
5523
5524
        return true;
5525
    }
5526
5527
    /**
5528
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5529
     *
5530
     * @param int $id DB ID of the item
5531
     */
5532
    public function set_previous_item($id)
5533
    {
5534
        if ($this->debug > 0) {
5535
            error_log('In learnpath::set_previous_item()', 0);
5536
        }
5537
        $this->last = $id;
5538
    }
5539
5540
    /**
5541
     * Sets use_max_score.
5542
     *
5543
     * @param int $use_max_score Optional string giving the new location of this learnpath
5544
     *
5545
     * @return bool True on success / False on error
5546
     */
5547
    public function set_use_max_score($use_max_score = 1)
5548
    {
5549
        if ($this->debug > 0) {
5550
            error_log('In learnpath::set_use_max_score()', 0);
5551
        }
5552
        $use_max_score = (int) $use_max_score;
5553
        $this->use_max_score = $use_max_score;
5554
        $table = Database::get_course_table(TABLE_LP_MAIN);
5555
        $lp_id = $this->get_id();
5556
        $sql = "UPDATE $table SET
5557
                    use_max_score = '".$this->use_max_score."'
5558
                WHERE iid = $lp_id";
5559
5560
        if ($this->debug > 2) {
5561
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5562
        }
5563
        Database::query($sql);
5564
5565
        return true;
5566
    }
5567
5568
    /**
5569
     * Sets and saves the expired_on date.
5570
     *
5571
     * @param string $expired_on Optional string giving the new author of this learnpath
5572
     *
5573
     * @throws \Doctrine\ORM\OptimisticLockException
5574
     *
5575
     * @return bool Returns true if author's name is not empty
5576
     */
5577
    public function set_expired_on($expired_on)
5578
    {
5579
        if ($this->debug > 0) {
5580
            error_log('In learnpath::set_expired_on()', 0);
5581
        }
5582
5583
        $em = Database::getManager();
5584
        /** @var CLp $lp */
5585
        $lp = $em
5586
            ->getRepository('ChamiloCourseBundle:CLp')
5587
            ->findOneBy(
5588
                [
5589
                    'iid' => $this->get_id(),
5590
                ]
5591
            );
5592
5593
        if (!$lp) {
5594
            return false;
5595
        }
5596
5597
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5598
5599
        $lp->setExpiredOn($this->expired_on);
5600
        $em->persist($lp);
5601
        $em->flush();
5602
5603
        if ($this->debug > 2) {
5604
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5605
        }
5606
5607
        return true;
5608
    }
5609
5610
    /**
5611
     * Sets and saves the publicated_on date.
5612
     *
5613
     * @param string $publicated_on Optional string giving the new author of this learnpath
5614
     *
5615
     * @throws \Doctrine\ORM\OptimisticLockException
5616
     *
5617
     * @return bool Returns true if author's name is not empty
5618
     */
5619
    public function set_publicated_on($publicated_on)
5620
    {
5621
        if ($this->debug > 0) {
5622
            error_log('In learnpath::set_expired_on()', 0);
5623
        }
5624
5625
        $em = Database::getManager();
5626
        /** @var CLp $lp */
5627
        $lp = $em
5628
            ->getRepository('ChamiloCourseBundle:CLp')
5629
            ->findOneBy(
5630
                [
5631
                    'iid' => $this->get_id(),
5632
                ]
5633
            );
5634
5635
        if (!$lp) {
5636
            return false;
5637
        }
5638
5639
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5640
        $lp->setPublicatedOn($this->publicated_on);
5641
        $em->persist($lp);
5642
        $em->flush();
5643
5644
        if ($this->debug > 2) {
5645
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5646
        }
5647
5648
        return true;
5649
    }
5650
5651
    /**
5652
     * Sets and saves the expired_on date.
5653
     *
5654
     * @return bool Returns true if author's name is not empty
5655
     */
5656
    public function set_modified_on()
5657
    {
5658
        if ($this->debug > 0) {
5659
            error_log('In learnpath::set_expired_on()', 0);
5660
        }
5661
        $this->modified_on = api_get_utc_datetime();
5662
        $table = Database::get_course_table(TABLE_LP_MAIN);
5663
        $lp_id = $this->get_id();
5664
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5665
                WHERE iid = $lp_id";
5666
        if ($this->debug > 2) {
5667
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5668
        }
5669
        Database::query($sql);
5670
5671
        return true;
5672
    }
5673
5674
    /**
5675
     * Sets the object's error message.
5676
     *
5677
     * @param string $error Error message. If empty, reinits the error string
5678
     */
5679
    public function set_error_msg($error = '')
5680
    {
5681
        if ($this->debug > 0) {
5682
            error_log('In learnpath::set_error_msg()', 0);
5683
        }
5684
        if (empty($error)) {
5685
            $this->error = '';
5686
        } else {
5687
            $this->error .= $error;
5688
        }
5689
    }
5690
5691
    /**
5692
     * Launches the current item if not 'sco'
5693
     * (starts timer and make sure there is a record ready in the DB).
5694
     *
5695
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5696
     *
5697
     * @return bool
5698
     */
5699
    public function start_current_item($allow_new_attempt = false)
5700
    {
5701
        $debug = $this->debug;
5702
        if ($debug) {
5703
            error_log('In learnpath::start_current_item()');
5704
            error_log('current: '.$this->current);
5705
        }
5706
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5707
            $type = $this->get_type();
5708
            $item_type = $this->items[$this->current]->get_type();
5709
            if (($type == 2 && $item_type != 'sco') ||
5710
                ($type == 3 && $item_type != 'au') ||
5711
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5712
            ) {
5713
                if ($debug) {
5714
                    error_log('item type: '.$item_type);
5715
                    error_log('lp type: '.$type);
5716
                }
5717
                $this->items[$this->current]->open($allow_new_attempt);
5718
                $this->autocomplete_parents($this->current);
5719
                $prereq_check = $this->prerequisites_match($this->current);
5720
                if ($debug) {
5721
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5722
                }
5723
                $this->items[$this->current]->save(false, $prereq_check);
5724
            }
5725
            // If sco, then it is supposed to have been updated by some other call.
5726
            if ($item_type == 'sco') {
5727
                $this->items[$this->current]->restart();
5728
            }
5729
        }
5730
        if ($debug) {
5731
            error_log('lp_view_session_id');
5732
            error_log($this->lp_view_session_id);
5733
            error_log('api session id');
5734
            error_log(api_get_session_id());
5735
            error_log('End of learnpath::start_current_item()');
5736
        }
5737
5738
        return true;
5739
    }
5740
5741
    /**
5742
     * Stops the processing and counters for the old item (as held in $this->last).
5743
     *
5744
     * @return bool True/False
5745
     */
5746
    public function stop_previous_item()
5747
    {
5748
        $debug = $this->debug;
5749
        if ($debug) {
5750
            error_log('In learnpath::stop_previous_item()', 0);
5751
        }
5752
5753
        if ($this->last != 0 && $this->last != $this->current &&
5754
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5755
        ) {
5756
            if ($debug) {
5757
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5758
            }
5759
            switch ($this->get_type()) {
5760
                case '3':
5761
                    if ($this->items[$this->last]->get_type() != 'au') {
5762
                        if ($debug) {
5763
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5764
                        }
5765
                        $this->items[$this->last]->close();
5766
                    } else {
5767
                        if ($debug) {
5768
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5769
                        }
5770
                    }
5771
                    break;
5772
                case '2':
5773
                    if ($this->items[$this->last]->get_type() != 'sco') {
5774
                        if ($debug) {
5775
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5776
                        }
5777
                        $this->items[$this->last]->close();
5778
                    } else {
5779
                        if ($debug) {
5780
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5781
                        }
5782
                    }
5783
                    break;
5784
                case '1':
5785
                default:
5786
                    if ($debug) {
5787
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5788
                    }
5789
                    $this->items[$this->last]->close();
5790
                    break;
5791
            }
5792
        } else {
5793
            if ($debug) {
5794
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5795
            }
5796
5797
            return false;
5798
        }
5799
5800
        return true;
5801
    }
5802
5803
    /**
5804
     * Updates the default view mode from fullscreen to embedded and inversely.
5805
     *
5806
     * @return string The current default view mode ('fullscreen' or 'embedded')
5807
     */
5808
    public function update_default_view_mode()
5809
    {
5810
        if ($this->debug > 0) {
5811
            error_log('In learnpath::update_default_view_mode()', 0);
5812
        }
5813
        $table = Database::get_course_table(TABLE_LP_MAIN);
5814
        $sql = "SELECT * FROM $table
5815
                WHERE iid = ".$this->get_id();
5816
        $res = Database::query($sql);
5817
        if (Database::num_rows($res) > 0) {
5818
            $row = Database::fetch_array($res);
5819
            $default_view_mode = $row['default_view_mod'];
5820
            $view_mode = $default_view_mode;
5821
            switch ($default_view_mode) {
5822
                case 'fullscreen': // default with popup
5823
                    $view_mode = 'embedded';
5824
                    break;
5825
                case 'embedded': // default view with left menu
5826
                    $view_mode = 'embedframe';
5827
                    break;
5828
                case 'embedframe': //folded menu
5829
                    $view_mode = 'impress';
5830
                    break;
5831
                case 'impress':
5832
                    $view_mode = 'fullscreen';
5833
                    break;
5834
            }
5835
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5836
                    WHERE iid = ".$this->get_id();
5837
            Database::query($sql);
5838
            $this->mode = $view_mode;
5839
5840
            return $view_mode;
5841
        } else {
5842
            if ($this->debug > 2) {
5843
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5844
            }
5845
        }
5846
5847
        return -1;
5848
    }
5849
5850
    /**
5851
     * Updates the default behaviour about auto-commiting SCORM updates.
5852
     *
5853
     * @return bool True if auto-commit has been set to 'on', false otherwise
5854
     */
5855
    public function update_default_scorm_commit()
5856
    {
5857
        if ($this->debug > 0) {
5858
            error_log('In learnpath::update_default_scorm_commit()', 0);
5859
        }
5860
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5861
        $sql = "SELECT * FROM $lp_table
5862
                WHERE iid = ".$this->get_id();
5863
        $res = Database::query($sql);
5864
        if (Database::num_rows($res) > 0) {
5865
            $row = Database::fetch_array($res);
5866
            $force = $row['force_commit'];
5867
            if ($force == 1) {
5868
                $force = 0;
5869
                $force_return = false;
5870
            } elseif ($force == 0) {
5871
                $force = 1;
5872
                $force_return = true;
5873
            }
5874
            $sql = "UPDATE $lp_table SET force_commit = $force
5875
                    WHERE iid = ".$this->get_id();
5876
            Database::query($sql);
5877
            $this->force_commit = $force_return;
5878
5879
            return $force_return;
5880
        } else {
5881
            if ($this->debug > 2) {
5882
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5883
            }
5884
        }
5885
5886
        return -1;
5887
    }
5888
5889
    /**
5890
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5891
     *
5892
     * @return bool True on success, false on failure
5893
     */
5894
    public function update_display_order()
5895
    {
5896
        $course_id = api_get_course_int_id();
5897
        $table = Database::get_course_table(TABLE_LP_MAIN);
5898
        $sql = "SELECT * FROM $table 
5899
                WHERE c_id = $course_id 
5900
                ORDER BY display_order";
5901
        $res = Database::query($sql);
5902
        if ($res === false) {
5903
            return false;
5904
        }
5905
5906
        $num = Database::num_rows($res);
5907
        // First check the order is correct, globally (might be wrong because
5908
        // of versions < 1.8.4).
5909
        if ($num > 0) {
5910
            $i = 1;
5911
            while ($row = Database::fetch_array($res)) {
5912
                if ($row['display_order'] != $i) {
5913
                    // If we find a gap in the order, we need to fix it.
5914
                    $sql = "UPDATE $table SET display_order = $i
5915
                            WHERE iid = ".$row['iid'];
5916
                    Database::query($sql);
5917
                }
5918
                $i++;
5919
            }
5920
        }
5921
5922
        return true;
5923
    }
5924
5925
    /**
5926
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5927
     *
5928
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5929
     */
5930
    public function update_reinit()
5931
    {
5932
        if ($this->debug > 0) {
5933
            error_log('In learnpath::update_reinit()', 0);
5934
        }
5935
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5936
        $sql = "SELECT * FROM $lp_table
5937
                WHERE iid = ".$this->get_id();
5938
        $res = Database::query($sql);
5939
        if (Database::num_rows($res) > 0) {
5940
            $row = Database::fetch_array($res);
5941
            $force = $row['prevent_reinit'];
5942
            if ($force == 1) {
5943
                $force = 0;
5944
            } elseif ($force == 0) {
5945
                $force = 1;
5946
            }
5947
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5948
                    WHERE iid = ".$this->get_id();
5949
            Database::query($sql);
5950
            $this->prevent_reinit = $force;
5951
5952
            return $force;
5953
        } else {
5954
            if ($this->debug > 2) {
5955
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5956
            }
5957
        }
5958
5959
        return -1;
5960
    }
5961
5962
    /**
5963
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5964
     *
5965
     * @return string 'single', 'multi' or 'seriousgame'
5966
     *
5967
     * @author ndiechburg <[email protected]>
5968
     */
5969
    public function get_attempt_mode()
5970
    {
5971
        //Set default value for seriousgame_mode
5972
        if (!isset($this->seriousgame_mode)) {
5973
            $this->seriousgame_mode = 0;
5974
        }
5975
        // Set default value for prevent_reinit
5976
        if (!isset($this->prevent_reinit)) {
5977
            $this->prevent_reinit = 1;
5978
        }
5979
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5980
            return 'seriousgame';
5981
        }
5982
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5983
            return 'single';
5984
        }
5985
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5986
            return 'multiple';
5987
        }
5988
5989
        return 'single';
5990
    }
5991
5992
    /**
5993
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5994
     *
5995
     * @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...
5996
     *
5997
     * @return bool
5998
     *
5999
     * @author ndiechburg <[email protected]>
6000
     */
6001
    public function set_attempt_mode($mode)
6002
    {
6003
        switch ($mode) {
6004
            case 'seriousgame':
6005
                $sg_mode = 1;
6006
                $prevent_reinit = 1;
6007
                break;
6008
            case 'single':
6009
                $sg_mode = 0;
6010
                $prevent_reinit = 1;
6011
                break;
6012
            case 'multiple':
6013
                $sg_mode = 0;
6014
                $prevent_reinit = 0;
6015
                break;
6016
            default:
6017
                $sg_mode = 0;
6018
                $prevent_reinit = 0;
6019
                break;
6020
        }
6021
        $this->prevent_reinit = $prevent_reinit;
6022
        $this->seriousgame_mode = $sg_mode;
6023
        $table = Database::get_course_table(TABLE_LP_MAIN);
6024
        $sql = "UPDATE $table SET
6025
                prevent_reinit = $prevent_reinit ,
6026
                seriousgame_mode = $sg_mode
6027
                WHERE iid = ".$this->get_id();
6028
        $res = Database::query($sql);
6029
        if ($res) {
6030
            return true;
6031
        } else {
6032
            return false;
6033
        }
6034
    }
6035
6036
    /**
6037
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
6038
     *
6039
     * @author ndiechburg <[email protected]>
6040
     */
6041
    public function switch_attempt_mode()
6042
    {
6043
        if ($this->debug > 0) {
6044
            error_log('In learnpath::switch_attempt_mode()', 0);
6045
        }
6046
        $mode = $this->get_attempt_mode();
6047
        switch ($mode) {
6048
            case 'single':
6049
                $next_mode = 'multiple';
6050
                break;
6051
            case 'multiple':
6052
                $next_mode = 'seriousgame';
6053
                break;
6054
            case 'seriousgame':
6055
                $next_mode = 'single';
6056
                break;
6057
            default:
6058
                $next_mode = 'single';
6059
                break;
6060
        }
6061
        $this->set_attempt_mode($next_mode);
6062
    }
6063
6064
    /**
6065
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
6066
     * but possibility to do again a completed item.
6067
     *
6068
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
6069
     *
6070
     * @author ndiechburg <[email protected]>
6071
     */
6072
    public function set_seriousgame_mode()
6073
    {
6074
        if ($this->debug > 0) {
6075
            error_log('In learnpath::set_seriousgame_mode()', 0);
6076
        }
6077
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6078
        $sql = "SELECT * FROM $lp_table 
6079
                WHERE iid = ".$this->get_id();
6080
        $res = Database::query($sql);
6081
        if (Database::num_rows($res) > 0) {
6082
            $row = Database::fetch_array($res);
6083
            $force = $row['seriousgame_mode'];
6084
            if ($force == 1) {
6085
                $force = 0;
6086
            } elseif ($force == 0) {
6087
                $force = 1;
6088
            }
6089
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6090
			        WHERE iid = ".$this->get_id();
6091
            Database::query($sql);
6092
            $this->seriousgame_mode = $force;
6093
6094
            return $force;
6095
        } else {
6096
            if ($this->debug > 2) {
6097
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
6098
            }
6099
        }
6100
6101
        return -1;
6102
    }
6103
6104
    /**
6105
     * Updates the "scorm_debug" value that shows or hide the debug window.
6106
     *
6107
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6108
     */
6109
    public function update_scorm_debug()
6110
    {
6111
        if ($this->debug > 0) {
6112
            error_log('In learnpath::update_scorm_debug()', 0);
6113
        }
6114
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6115
        $sql = "SELECT * FROM $lp_table
6116
                WHERE iid = ".$this->get_id();
6117
        $res = Database::query($sql);
6118
        if (Database::num_rows($res) > 0) {
6119
            $row = Database::fetch_array($res);
6120
            $force = $row['debug'];
6121
            if ($force == 1) {
6122
                $force = 0;
6123
            } elseif ($force == 0) {
6124
                $force = 1;
6125
            }
6126
            $sql = "UPDATE $lp_table SET debug = $force
6127
                    WHERE iid = ".$this->get_id();
6128
            Database::query($sql);
6129
            $this->scorm_debug = $force;
6130
6131
            return $force;
6132
        } else {
6133
            if ($this->debug > 2) {
6134
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6135
            }
6136
        }
6137
6138
        return -1;
6139
    }
6140
6141
    /**
6142
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6143
     *
6144
     * @author Kevin Van Den Haute
6145
     *
6146
     * @param  array
6147
     */
6148
    public function tree_array($array)
6149
    {
6150
        if ($this->debug > 1) {
6151
            error_log('In learnpath::tree_array()', 0);
6152
        }
6153
        $array = $this->sort_tree_array($array);
6154
        $this->create_tree_array($array);
6155
    }
6156
6157
    /**
6158
     * Creates an array with the elements of the learning path tree in it.
6159
     *
6160
     * @author Kevin Van Den Haute
6161
     *
6162
     * @param array $array
6163
     * @param int   $parent
6164
     * @param int   $depth
6165
     * @param array $tmp
6166
     */
6167
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6168
    {
6169
        if ($this->debug > 1) {
6170
            error_log('In learnpath::create_tree_array())', 0);
6171
        }
6172
6173
        if (is_array($array)) {
6174
            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...
6175
                if ($array[$i]['parent_item_id'] == $parent) {
6176
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6177
                        $tmp[] = $array[$i]['parent_item_id'];
6178
                        $depth++;
6179
                    }
6180
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6181
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6182
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6183
6184
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6185
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6186
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6187
                    $this->arrMenu[] = [
6188
                        'id' => $array[$i]['id'],
6189
                        'ref' => $ref,
6190
                        'item_type' => $array[$i]['item_type'],
6191
                        'title' => $array[$i]['title'],
6192
                        'path' => $path,
6193
                        'description' => $array[$i]['description'],
6194
                        'parent_item_id' => $array[$i]['parent_item_id'],
6195
                        'previous_item_id' => $array[$i]['previous_item_id'],
6196
                        'next_item_id' => $array[$i]['next_item_id'],
6197
                        'min_score' => $array[$i]['min_score'],
6198
                        'max_score' => $array[$i]['max_score'],
6199
                        'mastery_score' => $array[$i]['mastery_score'],
6200
                        'display_order' => $array[$i]['display_order'],
6201
                        'prerequisite' => $preq,
6202
                        'depth' => $depth,
6203
                        'audio' => $audio,
6204
                        'prerequisite_min_score' => $prerequisiteMinScore,
6205
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6206
                    ];
6207
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6208
                }
6209
            }
6210
        }
6211
    }
6212
6213
    /**
6214
     * Sorts a multi dimensional array by parent id and display order.
6215
     *
6216
     * @author Kevin Van Den Haute
6217
     *
6218
     * @param array $array (array with al the learning path items in it)
6219
     *
6220
     * @return array
6221
     */
6222
    public function sort_tree_array($array)
6223
    {
6224
        foreach ($array as $key => $row) {
6225
            $parent[$key] = $row['parent_item_id'];
6226
            $position[$key] = $row['display_order'];
6227
        }
6228
6229
        if (count($array) > 0) {
6230
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6231
        }
6232
6233
        return $array;
6234
    }
6235
6236
    /**
6237
     * Function that creates a html list of learning path items so that we can add audio files to them.
6238
     *
6239
     * @author Kevin Van Den Haute
6240
     *
6241
     * @return string
6242
     */
6243
    public function overview()
6244
    {
6245
        if ($this->debug > 0) {
6246
            error_log('In learnpath::overview()', 0);
6247
        }
6248
6249
        $return = '';
6250
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6251
6252
        // we need to start a form when we want to update all the mp3 files
6253
        if ($update_audio == 'true') {
6254
            $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">';
6255
        }
6256
        $return .= '<div id="message"></div>';
6257
        if (count($this->items) == 0) {
6258
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6259
        } else {
6260
            $return_audio = '<table class="data_table">';
6261
            $return_audio .= '<tr>';
6262
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6263
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6264
            $return_audio .= '</tr>';
6265
6266
            if ($update_audio != 'true') {
6267
                $return .= '<div class="col-md-12">';
6268
                $return .= self::return_new_tree($update_audio);
6269
                $return .= '</div>';
6270
                $return .= Display::div(
6271
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6272
                    ['style' => 'float:left; margin-top:15px;width:100%']
6273
                );
6274
            } else {
6275
                $return_audio .= self::return_new_tree($update_audio);
6276
                $return .= $return_audio.'</table>';
6277
            }
6278
6279
            // We need to close the form when we are updating the mp3 files.
6280
            if ($update_audio == 'true') {
6281
                $return .= '<div class="footer-audio">';
6282
                $return .= Display::button(
6283
                    'save_audio',
6284
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6285
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6286
                );
6287
                $return .= '</div>';
6288
            }
6289
        }
6290
6291
        // We need to close the form when we are updating the mp3 files.
6292
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6293
            $return .= '</form>';
6294
        }
6295
6296
        return $return;
6297
    }
6298
6299
    /**
6300
     * @param string $update_audio
6301
     *
6302
     * @return array
6303
     */
6304
    public function processBuildMenuElements($update_audio = 'false')
6305
    {
6306
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6307
        $course_id = api_get_course_int_id();
6308
        $table = Database::get_course_table(TABLE_LP_ITEM);
6309
6310
        $sql = "SELECT * FROM $table
6311
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6312
6313
        $result = Database::query($sql);
6314
        $arrLP = [];
6315
        while ($row = Database::fetch_array($result)) {
6316
            $arrLP[] = [
6317
                'id' => $row['iid'],
6318
                'item_type' => $row['item_type'],
6319
                'title' => Security::remove_XSS($row['title']),
6320
                'path' => $row['path'],
6321
                'description' => Security::remove_XSS($row['description']),
6322
                'parent_item_id' => $row['parent_item_id'],
6323
                'previous_item_id' => $row['previous_item_id'],
6324
                'next_item_id' => $row['next_item_id'],
6325
                'max_score' => $row['max_score'],
6326
                'min_score' => $row['min_score'],
6327
                'mastery_score' => $row['mastery_score'],
6328
                'prerequisite' => $row['prerequisite'],
6329
                'display_order' => $row['display_order'],
6330
                'audio' => $row['audio'],
6331
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6332
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6333
            ];
6334
        }
6335
6336
        $this->tree_array($arrLP);
6337
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6338
        unset($this->arrMenu);
6339
        $default_data = null;
6340
        $default_content = null;
6341
        $elements = [];
6342
        $return_audio = null;
6343
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6344
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6345
6346
        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...
6347
            $title = $arrLP[$i]['title'];
6348
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6349
6350
            // Link for the documents
6351
            if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6352
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6353
                $title_cut = Display::url(
6354
                    $title_cut,
6355
                    $url,
6356
                    [
6357
                        'class' => 'ajax moved',
6358
                        'data-title' => $title,
6359
                        'title' => $title,
6360
                    ]
6361
                );
6362
            }
6363
6364
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6365
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6366
                Session::write('pathItem', $arrLP[$i]['path']);
6367
            }
6368
6369
            if (($i % 2) == 0) {
6370
                $oddClass = 'row_odd';
6371
            } else {
6372
                $oddClass = 'row_even';
6373
            }
6374
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6375
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6376
6377
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6378
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6379
            } else {
6380
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6381
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6382
                } else {
6383
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6384
                        $icon = Display::return_icon('certificate.png');
6385
                    } else {
6386
                        $icon = Display::return_icon('folder_document.png');
6387
                    }
6388
                }
6389
            }
6390
6391
            // The audio column.
6392
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6393
            $audio = '';
6394
            if (!$update_audio || $update_audio != 'true') {
6395
                if (empty($arrLP[$i]['audio'])) {
6396
                    $audio .= '';
6397
                }
6398
            } else {
6399
                $types = self::getChapterTypes();
6400
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6401
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6402
                    if (!empty($arrLP[$i]['audio'])) {
6403
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6404
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6405
                    }
6406
                }
6407
            }
6408
6409
            $return_audio .= Display::span($icon.' '.$title).
6410
                Display::tag(
6411
                    'td',
6412
                    $audio,
6413
                    ['style' => '']
6414
                );
6415
            $return_audio .= '</td>';
6416
            $move_icon = '';
6417
            $move_item_icon = '';
6418
            $edit_icon = '';
6419
            $delete_icon = '';
6420
            $audio_icon = '';
6421
            $prerequisities_icon = '';
6422
            $forumIcon = '';
6423
            $previewIcon = '';
6424
            $pluginCalendarIcon = '';
6425
6426
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6427
            $plugin = null;
6428
            if ($pluginCalendar) {
6429
                $plugin = LearningCalendarPlugin::create();
6430
            }
6431
6432
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6433
6434
            if ($is_allowed_to_edit) {
6435
                if (!$update_audio || $update_audio != 'true') {
6436
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6437
                        $move_icon .= '<a class="moved" href="#">';
6438
                        $move_icon .= Display::return_icon(
6439
                            'move_everywhere.png',
6440
                            get_lang('Move'),
6441
                            [],
6442
                            ICON_SIZE_TINY
6443
                        );
6444
                        $move_icon .= '</a>';
6445
                    }
6446
                }
6447
6448
                // No edit for this item types
6449
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6450
                    if ($arrLP[$i]['item_type'] != 'dir') {
6451
                        $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">';
6452
                        $edit_icon .= Display::return_icon(
6453
                            'edit.png',
6454
                            get_lang('LearnpathEditModule'),
6455
                            [],
6456
                            ICON_SIZE_TINY
6457
                        );
6458
                        $edit_icon .= '</a>';
6459
6460
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6461
                            $forumThread = null;
6462
                            if (isset($this->items[$arrLP[$i]['id']])) {
6463
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6464
                                    $this->course_int_id,
6465
                                    $this->lp_session_id
6466
                                );
6467
                            }
6468
                            if ($forumThread) {
6469
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6470
                                        'action' => 'dissociate_forum',
6471
                                        'id' => $arrLP[$i]['id'],
6472
                                        'lp_id' => $this->lp_id,
6473
                                    ]);
6474
                                $forumIcon = Display::url(
6475
                                    Display::return_icon(
6476
                                        'forum.png',
6477
                                        get_lang('DissociateForumToLPItem'),
6478
                                        [],
6479
                                        ICON_SIZE_TINY
6480
                                    ),
6481
                                    $forumIconUrl,
6482
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6483
                                );
6484
                            } else {
6485
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6486
                                        'action' => 'create_forum',
6487
                                        'id' => $arrLP[$i]['id'],
6488
                                        'lp_id' => $this->lp_id,
6489
                                    ]);
6490
                                $forumIcon = Display::url(
6491
                                    Display::return_icon(
6492
                                        'forum.png',
6493
                                        get_lang('AssociateForumToLPItem'),
6494
                                        [],
6495
                                        ICON_SIZE_TINY
6496
                                    ),
6497
                                    $forumIconUrl,
6498
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6499
                                );
6500
                            }
6501
                        }
6502
                    } else {
6503
                        $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">';
6504
                        $edit_icon .= Display::return_icon(
6505
                            'edit.png',
6506
                            get_lang('LearnpathEditModule'),
6507
                            [],
6508
                            ICON_SIZE_TINY
6509
                        );
6510
                        $edit_icon .= '</a>';
6511
                    }
6512
                } else {
6513
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6514
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6515
                        $edit_icon .= Display::return_icon(
6516
                            'edit.png',
6517
                            get_lang('Edit'),
6518
                            [],
6519
                            ICON_SIZE_TINY
6520
                        );
6521
                        $edit_icon .= '</a>';
6522
                    }
6523
                }
6524
6525
                if ($pluginCalendar) {
6526
                    $pluginLink = $pluginUrl.
6527
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6528
6529
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6530
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6531
                    if ($itemInfo && $itemInfo['value'] == 1) {
6532
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6533
                    }
6534
                    $pluginCalendarIcon = Display::url(
6535
                        $iconCalendar,
6536
                        $pluginLink,
6537
                        ['class' => 'btn btn-default']
6538
                    );
6539
                }
6540
6541
                $delete_icon .= ' <a 
6542
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6543
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6544
                    class="btn btn-default">';
6545
                $delete_icon .= Display::return_icon(
6546
                    'delete.png',
6547
                    get_lang('LearnpathDeleteModule'),
6548
                    [],
6549
                    ICON_SIZE_TINY
6550
                );
6551
                $delete_icon .= '</a>';
6552
6553
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6554
                $previewImage = Display::return_icon(
6555
                    'preview_view.png',
6556
                    get_lang('Preview'),
6557
                    [],
6558
                    ICON_SIZE_TINY
6559
                );
6560
6561
                switch ($arrLP[$i]['item_type']) {
6562
                    case TOOL_DOCUMENT:
6563
                    case TOOL_LP_FINAL_ITEM:
6564
                    case TOOL_READOUT_TEXT:
6565
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6566
                        $previewIcon = Display::url(
6567
                            $previewImage,
6568
                            $urlPreviewLink,
6569
                            [
6570
                                'target' => '_blank',
6571
                                'class' => 'btn btn-default',
6572
                                'data-title' => $arrLP[$i]['title'],
6573
                                'title' => $arrLP[$i]['title'],
6574
                            ]
6575
                        );
6576
                        break;
6577
                    case TOOL_THREAD:
6578
                    case TOOL_FORUM:
6579
                    case TOOL_QUIZ:
6580
                    case TOOL_STUDENTPUBLICATION:
6581
                    case TOOL_LP_FINAL_ITEM:
6582
                    case TOOL_LINK:
6583
                        //$target = '';
6584
                        //$class = 'btn btn-default ajax';
6585
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6586
                        $class = 'btn btn-default';
6587
                        $target = '_blank';
6588
                        //}
6589
6590
                        $link = self::rl_get_resource_link_for_learnpath(
6591
                            $this->course_int_id,
6592
                            $this->lp_id,
6593
                            $arrLP[$i]['id'],
6594
                            0
6595
                        );
6596
                        $previewIcon = Display::url(
6597
                            $previewImage,
6598
                            $link,
6599
                            [
6600
                                'class' => $class,
6601
                                'data-title' => $arrLP[$i]['title'],
6602
                                'title' => $arrLP[$i]['title'],
6603
                                'target' => $target,
6604
                            ]
6605
                        );
6606
                        break;
6607
                    default:
6608
                        $previewIcon = Display::url(
6609
                            $previewImage,
6610
                            $url.'&action=view_item',
6611
                            ['class' => 'btn btn-default', 'target' => '_blank']
6612
                        );
6613
                        break;
6614
                }
6615
6616
                if ($arrLP[$i]['item_type'] != 'dir') {
6617
                    $prerequisities_icon = Display::url(
6618
                        Display::return_icon(
6619
                            'accept.png',
6620
                            get_lang('LearnpathPrerequisites'),
6621
                            [],
6622
                            ICON_SIZE_TINY
6623
                        ),
6624
                        $url.'&action=edit_item_prereq',
6625
                        ['class' => 'btn btn-default']
6626
                    );
6627
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6628
                        $move_item_icon = Display::url(
6629
                            Display::return_icon(
6630
                                'move.png',
6631
                                get_lang('Move'),
6632
                                [],
6633
                                ICON_SIZE_TINY
6634
                            ),
6635
                            $url.'&action=move_item',
6636
                            ['class' => 'btn btn-default']
6637
                        );
6638
                    }
6639
                    $audio_icon = Display::url(
6640
                        Display::return_icon(
6641
                            'audio.png',
6642
                            get_lang('UplUpload'),
6643
                            [],
6644
                            ICON_SIZE_TINY
6645
                        ),
6646
                        $url.'&action=add_audio',
6647
                        ['class' => 'btn btn-default']
6648
                    );
6649
                }
6650
            }
6651
            if ($update_audio != 'true') {
6652
                $row = $move_icon.' '.$icon.
6653
                    Display::span($title_cut).
6654
                    Display::tag(
6655
                        'div',
6656
                        "<div class=\"btn-group btn-group-xs\">
6657
                                    $previewIcon 
6658
                                    $audio 
6659
                                    $edit_icon 
6660
                                    $pluginCalendarIcon
6661
                                    $forumIcon 
6662
                                    $prerequisities_icon 
6663
                                    $move_item_icon 
6664
                                    $audio_icon 
6665
                                    $delete_icon
6666
                                </div>",
6667
                        ['class' => 'btn-toolbar button_actions']
6668
                    );
6669
            } else {
6670
                $row =
6671
                    Display::span($title.$icon).
6672
                    Display::span($audio, ['class' => 'button_actions']);
6673
            }
6674
6675
            $parent_id = $arrLP[$i]['parent_item_id'];
6676
            $default_data[$arrLP[$i]['id']] = $row;
6677
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6678
6679
            if (empty($parent_id)) {
6680
                $elements[$arrLP[$i]['id']]['data'] = $row;
6681
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6682
            } else {
6683
                $parent_arrays = [];
6684
                if ($arrLP[$i]['depth'] > 1) {
6685
                    //Getting list of parents
6686
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6687
                        foreach ($arrLP as $item) {
6688
                            if ($item['id'] == $parent_id) {
6689
                                if ($item['parent_item_id'] == 0) {
6690
                                    $parent_id = $item['id'];
6691
                                    break;
6692
                                } else {
6693
                                    $parent_id = $item['parent_item_id'];
6694
                                    if (empty($parent_arrays)) {
6695
                                        $parent_arrays[] = intval($item['id']);
6696
                                    }
6697
                                    $parent_arrays[] = $parent_id;
6698
                                    break;
6699
                                }
6700
                            }
6701
                        }
6702
                    }
6703
                }
6704
6705
                if (!empty($parent_arrays)) {
6706
                    $parent_arrays = array_reverse($parent_arrays);
6707
                    $val = '$elements';
6708
                    $x = 0;
6709
                    foreach ($parent_arrays as $item) {
6710
                        if ($x != count($parent_arrays) - 1) {
6711
                            $val .= '["'.$item.'"]["children"]';
6712
                        } else {
6713
                            $val .= '["'.$item.'"]["children"]';
6714
                        }
6715
                        $x++;
6716
                    }
6717
                    $val .= "";
6718
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6719
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6720
                } else {
6721
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6722
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6723
                }
6724
            }
6725
        }
6726
6727
        return [
6728
            'elements' => $elements,
6729
            'default_data' => $default_data,
6730
            'default_content' => $default_content,
6731
            'return_audio' => $return_audio,
6732
        ];
6733
    }
6734
6735
    /**
6736
     * @param string $updateAudio true/false strings
6737
     *
6738
     * @return string
6739
     */
6740
    public function returnLpItemList($updateAudio)
6741
    {
6742
        $result = $this->processBuildMenuElements($updateAudio);
6743
6744
        $html = self::print_recursive(
6745
            $result['elements'],
6746
            $result['default_data'],
6747
            $result['default_content']
6748
        );
6749
6750
        if (!empty($html)) {
6751
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6752
        }
6753
6754
        return $html;
6755
    }
6756
6757
    /**
6758
     * @param string $update_audio
6759
     * @param bool   $drop_element_here
6760
     *
6761
     * @return string
6762
     */
6763
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6764
    {
6765
        $return = '';
6766
        $result = $this->processBuildMenuElements($update_audio);
6767
6768
        $list = '<ul id="lp_item_list">';
6769
        $tree = $this->print_recursive(
6770
            $result['elements'],
6771
            $result['default_data'],
6772
            $result['default_content']
6773
        );
6774
6775
        if (!empty($tree)) {
6776
            $list .= $tree;
6777
        } else {
6778
            if ($drop_element_here) {
6779
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6780
            }
6781
        }
6782
        $list .= '</ul>';
6783
6784
        $return .= Display::panelCollapse(
6785
            $this->name,
6786
            $list,
6787
            'scorm-list',
6788
            null,
6789
            'scorm-list-accordion',
6790
            'scorm-list-collapse'
6791
        );
6792
6793
        if ($update_audio === 'true') {
6794
            $return = $result['return_audio'];
6795
        }
6796
6797
        return $return;
6798
    }
6799
6800
    /**
6801
     * @param array $elements
6802
     * @param array $default_data
6803
     * @param array $default_content
6804
     *
6805
     * @return string
6806
     */
6807
    public function print_recursive($elements, $default_data, $default_content)
6808
    {
6809
        $return = '';
6810
        foreach ($elements as $key => $item) {
6811
            if (isset($item['load_data']) || empty($item['data'])) {
6812
                $item['data'] = $default_data[$item['load_data']];
6813
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6814
            }
6815
            $sub_list = '';
6816
            if (isset($item['type']) && $item['type'] == 'dir') {
6817
                // empty value
6818
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6819
            }
6820
            if (empty($item['children'])) {
6821
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6822
                $active = null;
6823
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6824
                    $active = 'active';
6825
                }
6826
                $return .= Display::tag(
6827
                    'li',
6828
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6829
                    ['id' => $key, 'class' => 'record li_container']
6830
                );
6831
            } else {
6832
                // Sections
6833
                $data = '';
6834
                if (isset($item['children'])) {
6835
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6836
                }
6837
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6838
                $return .= Display::tag(
6839
                    'li',
6840
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6841
                    ['id' => $key, 'class' => 'record li_container']
6842
                );
6843
            }
6844
        }
6845
6846
        return $return;
6847
    }
6848
6849
    /**
6850
     * This function builds the action menu.
6851
     *
6852
     * @param bool $returnContent          Optional
6853
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6854
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6855
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6856
     *
6857
     * @return string
6858
     */
6859
    public function build_action_menu(
6860
        $returnContent = false,
6861
        $showRequirementButtons = true,
6862
        $isConfigPage = false,
6863
        $allowExpand = true
6864
    ) {
6865
        $actionsLeft = '';
6866
        $actionsRight = '';
6867
        $actionsLeft .= Display::url(
6868
            Display::return_icon(
6869
                'back.png',
6870
                get_lang('ReturnToLearningPaths'),
6871
                '',
6872
                ICON_SIZE_MEDIUM
6873
            ),
6874
            'lp_controller.php?'.api_get_cidreq()
6875
        );
6876
        $actionsLeft .= Display::url(
6877
            Display::return_icon(
6878
                'preview_view.png',
6879
                get_lang('Preview'),
6880
                '',
6881
                ICON_SIZE_MEDIUM
6882
            ),
6883
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6884
                'action' => 'view',
6885
                'lp_id' => $this->lp_id,
6886
                'isStudentView' => 'true',
6887
            ])
6888
        );
6889
6890
        $actionsLeft .= Display::url(
6891
            Display::return_icon(
6892
                'upload_audio.png',
6893
                get_lang('UpdateAllAudioFragments'),
6894
                '',
6895
                ICON_SIZE_MEDIUM
6896
            ),
6897
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6898
                'action' => 'admin_view',
6899
                'lp_id' => $this->lp_id,
6900
                'updateaudio' => 'true',
6901
            ])
6902
        );
6903
6904
        if (!$isConfigPage) {
6905
            $actionsLeft .= Display::url(
6906
                Display::return_icon(
6907
                    'settings.png',
6908
                    get_lang('CourseSettings'),
6909
                    '',
6910
                    ICON_SIZE_MEDIUM
6911
                ),
6912
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6913
                    'action' => 'edit',
6914
                    'lp_id' => $this->lp_id,
6915
                ])
6916
            );
6917
        } else {
6918
            $actionsLeft .= Display::url(
6919
                Display::return_icon(
6920
                    'edit.png',
6921
                    get_lang('Edit'),
6922
                    '',
6923
                    ICON_SIZE_MEDIUM
6924
                ),
6925
                'lp_controller.php?'.http_build_query([
6926
                    'action' => 'build',
6927
                    'lp_id' => $this->lp_id,
6928
                ]).'&'.api_get_cidreq()
6929
            );
6930
        }
6931
6932
        if ($allowExpand) {
6933
            $actionsLeft .= Display::url(
6934
                Display::return_icon(
6935
                    'expand.png',
6936
                    get_lang('Expand'),
6937
                    ['id' => 'expand'],
6938
                    ICON_SIZE_MEDIUM
6939
                ).
6940
                Display::return_icon(
6941
                    'contract.png',
6942
                    get_lang('Collapse'),
6943
                    ['id' => 'contract', 'class' => 'hide'],
6944
                    ICON_SIZE_MEDIUM
6945
                ),
6946
                '#',
6947
                ['role' => 'button', 'id' => 'hide_bar_template']
6948
            );
6949
        }
6950
6951
        if ($showRequirementButtons) {
6952
            $buttons = [
6953
                [
6954
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6955
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6956
                        'action' => 'set_previous_step_as_prerequisite',
6957
                        'lp_id' => $this->lp_id,
6958
                    ]),
6959
                ],
6960
                [
6961
                    'title' => get_lang('ClearAllPrerequisites'),
6962
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6963
                        'action' => 'clear_prerequisites',
6964
                        'lp_id' => $this->lp_id,
6965
                    ]),
6966
                ],
6967
            ];
6968
            $actionsRight = Display::groupButtonWithDropDown(
6969
                get_lang('PrerequisitesOptions'),
6970
                $buttons,
6971
                true
6972
            );
6973
        }
6974
6975
        $toolbar = Display::toolbarAction(
6976
            'actions-lp-controller',
6977
            [$actionsLeft, $actionsRight]
6978
        );
6979
6980
        if ($returnContent) {
6981
            return $toolbar;
6982
        }
6983
6984
        echo $toolbar;
6985
    }
6986
6987
    /**
6988
     * Creates the default learning path folder.
6989
     *
6990
     * @param array $course
6991
     * @param int   $creatorId
6992
     *
6993
     * @return bool
6994
     */
6995
    public static function generate_learning_path_folder($course, $creatorId = 0)
6996
    {
6997
        // Creating learning_path folder
6998
        $dir = '/learning_path';
6999
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7000
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7001
7002
        $folder = false;
7003
        $folderData = create_unexisting_directory(
7004
            $course,
7005
            $creatorId,
7006
            0,
7007
            null,
7008
            0,
7009
            $filepath,
7010
            $dir,
7011
            get_lang('LearningPaths'),
7012
            0
7013
        );
7014
7015
        if (!empty($folderData)) {
7016
            $folder = true;
7017
        }
7018
7019
        return $folder;
7020
    }
7021
7022
    /**
7023
     * @param array  $course
7024
     * @param string $lp_name
7025
     * @param int    $creatorId
7026
     *
7027
     * @return array
7028
     */
7029
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
7030
    {
7031
        $filepath = '';
7032
        $dir = '/learning_path/';
7033
7034
        if (empty($lp_name)) {
7035
            $lp_name = $this->name;
7036
        }
7037
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7038
7039
        $folder = self::generate_learning_path_folder($course, $creatorId);
7040
7041
        // Limits title size
7042
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
7043
        $dir = $dir.$title;
7044
7045
        // Creating LP folder
7046
        $documentId = null;
7047
        if ($folder) {
7048
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7049
            $folderData = create_unexisting_directory(
7050
                $course,
7051
                $creatorId,
7052
                0,
7053
                0,
7054
                0,
7055
                $filepath,
7056
                $dir,
7057
                $lp_name
7058
            );
7059
            if (!empty($folderData)) {
7060
                $folder = true;
7061
            }
7062
7063
            $documentId = $folderData->getIid();
7064
            /*} else {
7065
                $folder = true;
7066
            }*/
7067
7068
            $dir = $dir.'/';
7069
            if ($folder) {
7070
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
7071
            }
7072
        }
7073
7074
        /*if (empty($documentId)) {
7075
            $dir = api_remove_trailing_slash($dir);
7076
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
7077
        }*/
7078
7079
        $array = [
7080
            'dir' => $dir,
7081
            'filepath' => $filepath,
7082
            'folder' => $folder,
7083
            'id' => $documentId,
7084
        ];
7085
7086
        return $array;
7087
    }
7088
7089
    /**
7090
     * Create a new document //still needs some finetuning.
7091
     *
7092
     * @param array  $courseInfo
7093
     * @param string $content
7094
     * @param string $title
7095
     * @param string $extension
7096
     * @param int    $parentId
7097
     * @param int    $creatorId  creator id
7098
     *
7099
     * @return int
7100
     */
7101
    public function create_document(
7102
        $courseInfo,
7103
        $content = '',
7104
        $title = '',
7105
        $extension = 'html',
7106
        $parentId = 0,
7107
        $creatorId = 0
7108
    ) {
7109
        if (!empty($courseInfo)) {
7110
            $course_id = $courseInfo['real_id'];
7111
        } else {
7112
            $course_id = api_get_course_int_id();
7113
        }
7114
7115
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7116
        $sessionId = api_get_session_id();
7117
7118
        // Generates folder
7119
        $result = $this->generate_lp_folder($courseInfo);
7120
        $dir = $result['dir'];
7121
7122
        if (empty($parentId) || $parentId == '/') {
7123
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7124
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7125
7126
            if ($parentId === '/') {
7127
                $dir = '/';
7128
            }
7129
7130
            // Please, do not modify this dirname formatting.
7131
            if (strstr($dir, '..')) {
7132
                $dir = '/';
7133
            }
7134
7135
            if (!empty($dir[0]) && $dir[0] == '.') {
7136
                $dir = substr($dir, 1);
7137
            }
7138
            if (!empty($dir[0]) && $dir[0] != '/') {
7139
                $dir = '/'.$dir;
7140
            }
7141
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7142
                $dir .= '/';
7143
            }
7144
        } else {
7145
            $parentInfo = DocumentManager::get_document_data_by_id(
7146
                $parentId,
7147
                $courseInfo['code']
7148
            );
7149
            if (!empty($parentInfo)) {
7150
                $dir = $parentInfo['path'].'/';
7151
            }
7152
        }
7153
7154
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7155
        if (!is_dir($filepath)) {
7156
            $dir = '/';
7157
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7158
        }
7159
7160
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7161
        // is already escaped twice when it gets here.
7162
7163
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7164
        if (!empty($title)) {
7165
            $title = api_replace_dangerous_char(stripslashes($title));
7166
        } else {
7167
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7168
        }
7169
7170
        $title = disable_dangerous_file($title);
7171
        $filename = $title;
7172
        $content = !empty($content) ? $content : $_POST['content_lp'];
7173
        $tmp_filename = $filename;
7174
7175
        $i = 0;
7176
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7177
            $tmp_filename = $filename.'_'.++$i;
7178
        }
7179
7180
        $filename = $tmp_filename.'.'.$extension;
7181
        if ($extension == 'html') {
7182
            $content = stripslashes($content);
7183
            $content = str_replace(
7184
                api_get_path(WEB_COURSE_PATH),
7185
                api_get_path(REL_PATH).'courses/',
7186
                $content
7187
            );
7188
7189
            // Change the path of mp3 to absolute.
7190
7191
            // The first regexp deals with :// urls.
7192
            $content = preg_replace(
7193
                "|(flashvars=\"file=)([^:/]+)/|",
7194
                "$1".api_get_path(
7195
                    REL_COURSE_PATH
7196
                ).$courseInfo['path'].'/document/',
7197
                $content
7198
            );
7199
            // The second regexp deals with audio/ urls.
7200
            $content = preg_replace(
7201
                "|(flashvars=\"file=)([^/]+)/|",
7202
                "$1".api_get_path(
7203
                    REL_COURSE_PATH
7204
                ).$courseInfo['path'].'/document/$2/',
7205
                $content
7206
            );
7207
            // For flv player: To prevent edition problem with firefox,
7208
            // we have to use a strange tip (don't blame me please).
7209
            $content = str_replace(
7210
                '</body>',
7211
                '<style type="text/css">body{}</style></body>',
7212
                $content
7213
            );
7214
        }
7215
7216
        if (!file_exists($filepath.$filename)) {
7217
            if ($fp = @fopen($filepath.$filename, 'w')) {
7218
                fputs($fp, $content);
7219
                fclose($fp);
7220
7221
                $file_size = filesize($filepath.$filename);
7222
                $save_file_path = $dir.$filename;
7223
7224
                $document = DocumentManager::addDocument(
7225
                    $courseInfo,
7226
                    $save_file_path,
7227
                    'file',
7228
                    $file_size,
7229
                    $tmp_filename,
7230
                    '',
7231
                    0, //readonly
7232
                    true,
7233
                    null,
7234
                    $sessionId,
7235
                    $creatorId
7236
                );
7237
7238
                $document_id = $document->getId();
7239
7240
                if ($document_id) {
7241
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7242
                    $new_title = $originalTitle;
7243
7244
                    if ($new_comment || $new_title) {
7245
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7246
                        $ct = '';
7247
                        if ($new_comment) {
7248
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7249
                        }
7250
                        if ($new_title) {
7251
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7252
                        }
7253
7254
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7255
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7256
                        Database::query($sql);
7257
                    }
7258
                }
7259
7260
                return $document_id;
7261
            }
7262
        }
7263
    }
7264
7265
    /**
7266
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7267
     *
7268
     * @param array $_course array
7269
     */
7270
    public function edit_document($_course)
7271
    {
7272
        $course_id = api_get_course_int_id();
7273
        $urlAppend = api_get_configuration_value('url_append');
7274
        // Please, do not modify this dirname formatting.
7275
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7276
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7277
7278
        if (strstr($dir, '..')) {
7279
            $dir = '/';
7280
        }
7281
7282
        if (isset($dir[0]) && $dir[0] == '.') {
7283
            $dir = substr($dir, 1);
7284
        }
7285
7286
        if (isset($dir[0]) && $dir[0] != '/') {
7287
            $dir = '/'.$dir;
7288
        }
7289
7290
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7291
            $dir .= '/';
7292
        }
7293
7294
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7295
        if (!is_dir($filepath)) {
7296
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7297
        }
7298
7299
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7300
7301
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7302
            $document_id = (int) $_POST['path'];
7303
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7304
            if (empty($documentInfo)) {
7305
                // Try with iid
7306
                $table = Database::get_course_table(TABLE_DOCUMENT);
7307
                $sql = "SELECT id, path FROM $table 
7308
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7309
                $res_doc = Database::query($sql);
7310
                $row = Database::fetch_array($res_doc);
7311
                if ($row) {
7312
                    $document_id = $row['id'];
7313
                    $documentPath = $row['path'];
7314
                }
7315
            } else {
7316
                $documentPath = $documentInfo['path'];
7317
            }
7318
7319
            $content = stripslashes($_POST['content_lp']);
7320
            $file = $filepath.$documentPath;
7321
7322
            if (!file_exists($file)) {
7323
                return false;
7324
            }
7325
7326
            if ($fp = @fopen($file, 'w')) {
7327
                $content = str_replace(
7328
                    api_get_path(WEB_COURSE_PATH),
7329
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7330
                    $content
7331
                );
7332
                // Change the path of mp3 to absolute.
7333
                // The first regexp deals with :// urls.
7334
                $content = preg_replace(
7335
                    "|(flashvars=\"file=)([^:/]+)/|",
7336
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7337
                    $content
7338
                );
7339
                // The second regexp deals with audio/ urls.
7340
                $content = preg_replace(
7341
                    "|(flashvars=\"file=)([^:/]+)/|",
7342
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7343
                    $content
7344
                );
7345
                fputs($fp, $content);
7346
                fclose($fp);
7347
7348
                $sql = "UPDATE $table_doc SET
7349
                            title='".Database::escape_string($_POST['title'])."'
7350
                        WHERE c_id = $course_id AND id = ".$document_id;
7351
                Database::query($sql);
7352
            }
7353
        }
7354
    }
7355
7356
    /**
7357
     * Displays the selected item, with a panel for manipulating the item.
7358
     *
7359
     * @param int    $item_id
7360
     * @param string $msg
7361
     * @param bool   $show_actions
7362
     *
7363
     * @return string
7364
     */
7365
    public function display_item($item_id, $msg = null, $show_actions = true)
7366
    {
7367
        $course_id = api_get_course_int_id();
7368
        $return = '';
7369
        if (is_numeric($item_id)) {
7370
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7371
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7372
                    WHERE lp.iid = ".intval($item_id);
7373
            $result = Database::query($sql);
7374
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7375
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7376
7377
                // Prevents wrong parent selection for document, see Bug#1251.
7378
                if ($row['item_type'] != 'dir') {
7379
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7380
                }
7381
7382
                if ($show_actions) {
7383
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7384
                }
7385
                $return .= '<div style="padding:10px;">';
7386
7387
                if ($msg != '') {
7388
                    $return .= $msg;
7389
                }
7390
7391
                $return .= '<h3>'.$row['title'].'</h3>';
7392
7393
                switch ($row['item_type']) {
7394
                    case TOOL_THREAD:
7395
                        $link = $this->rl_get_resource_link_for_learnpath(
7396
                            $course_id,
7397
                            $row['lp_id'],
7398
                            $item_id,
7399
                            0
7400
                        );
7401
                        $return .= Display::url(
7402
                            get_lang('GoToThread'),
7403
                            $link,
7404
                            ['class' => 'btn btn-primary']
7405
                        );
7406
                        break;
7407
                    case TOOL_FORUM:
7408
                        $return .= Display::url(
7409
                            get_lang('GoToForum'),
7410
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7411
                            ['class' => 'btn btn-primary']
7412
                        );
7413
                        break;
7414
                    case TOOL_QUIZ:
7415
                        if (!empty($row['path'])) {
7416
                            $exercise = new Exercise();
7417
                            $exercise->read($row['path']);
7418
                            $return .= $exercise->description.'<br />';
7419
                            $return .= Display::url(
7420
                                get_lang('GoToExercise'),
7421
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7422
                                ['class' => 'btn btn-primary']
7423
                            );
7424
                        }
7425
                        break;
7426
                    case TOOL_LP_FINAL_ITEM:
7427
                        $return .= $this->getSavedFinalItem();
7428
                        break;
7429
                    case TOOL_DOCUMENT:
7430
                    case TOOL_READOUT_TEXT:
7431
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7432
                        $sql_doc = "SELECT path FROM $tbl_doc
7433
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7434
                        $result = Database::query($sql_doc);
7435
                        $path_file = Database::result($result, 0, 0);
7436
                        $path_parts = pathinfo($path_file);
7437
                        // TODO: Correct the following naive comparisons.
7438
                        if (in_array($path_parts['extension'], [
7439
                            'html',
7440
                            'txt',
7441
                            'png',
7442
                            'jpg',
7443
                            'JPG',
7444
                            'jpeg',
7445
                            'JPEG',
7446
                            'gif',
7447
                            'swf',
7448
                            'pdf',
7449
                            'htm',
7450
                        ])) {
7451
                            $return .= $this->display_document($row['path'], true, true);
7452
                        }
7453
                        break;
7454
                    case TOOL_HOTPOTATOES:
7455
                        $return .= $this->display_document($row['path'], false, true);
7456
                        break;
7457
                }
7458
                $return .= '</div>';
7459
            }
7460
        }
7461
7462
        return $return;
7463
    }
7464
7465
    /**
7466
     * Shows the needed forms for editing a specific item.
7467
     *
7468
     * @param int $item_id
7469
     *
7470
     * @throws Exception
7471
     * @throws HTML_QuickForm_Error
7472
     *
7473
     * @return string
7474
     */
7475
    public function display_edit_item($item_id)
7476
    {
7477
        $course_id = api_get_course_int_id();
7478
        $return = '';
7479
        $item_id = (int) $item_id;
7480
7481
        if (empty($item_id)) {
7482
            return '';
7483
        }
7484
7485
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7486
        $sql = "SELECT * FROM $tbl_lp_item
7487
                WHERE iid = ".$item_id;
7488
        $res = Database::query($sql);
7489
        $row = Database::fetch_array($res);
7490
        switch ($row['item_type']) {
7491
            case 'dir':
7492
            case 'asset':
7493
            case 'sco':
7494
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7495
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7496
                    $return .= $this->display_item_form(
7497
                        $row['item_type'],
7498
                        get_lang('EditCurrentChapter').' :',
7499
                        'edit',
7500
                        $item_id,
7501
                        $row
7502
                    );
7503
                } else {
7504
                    $return .= $this->display_item_form(
7505
                        $row['item_type'],
7506
                        get_lang('EditCurrentChapter').' :',
7507
                        'edit_item',
7508
                        $item_id,
7509
                        $row
7510
                    );
7511
                }
7512
                break;
7513
            case TOOL_DOCUMENT:
7514
            case TOOL_READOUT_TEXT:
7515
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7516
                $sql = "SELECT lp.*, doc.path as dir
7517
                        FROM $tbl_lp_item as lp
7518
                        LEFT JOIN $tbl_doc as doc
7519
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7520
                        WHERE
7521
                            doc.c_id = $course_id AND
7522
                            lp.iid = ".$item_id;
7523
                $res_step = Database::query($sql);
7524
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7525
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7526
7527
                if ($row['item_type'] === TOOL_DOCUMENT) {
7528
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7529
                }
7530
7531
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7532
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7533
                }
7534
                break;
7535
            case TOOL_LINK:
7536
                $linkId = (int) $row['path'];
7537
                if (!empty($linkId)) {
7538
                    $table = Database::get_course_table(TABLE_LINK);
7539
                    $sql = 'SELECT url FROM '.$table.'
7540
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7541
                    $res_link = Database::query($sql);
7542
                    $row_link = Database::fetch_array($res_link);
7543
                    if (empty($row_link)) {
7544
                        // Try with id
7545
                        $sql = 'SELECT url FROM '.$table.'
7546
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7547
                        $res_link = Database::query($sql);
7548
                        $row_link = Database::fetch_array($res_link);
7549
                    }
7550
7551
                    if (is_array($row_link)) {
7552
                        $row['url'] = $row_link['url'];
7553
                    }
7554
                }
7555
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7556
                $return .= $this->display_link_form('edit', $item_id, $row);
7557
                break;
7558
            case TOOL_LP_FINAL_ITEM:
7559
                Session::write('finalItem', true);
7560
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7561
                $sql = "SELECT lp.*, doc.path as dir
7562
                        FROM $tbl_lp_item as lp
7563
                        LEFT JOIN $tbl_doc as doc
7564
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7565
                        WHERE
7566
                            doc.c_id = $course_id AND
7567
                            lp.iid = ".$item_id;
7568
                $res_step = Database::query($sql);
7569
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7570
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7571
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7572
                break;
7573
            case TOOL_QUIZ:
7574
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7575
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7576
                break;
7577
            case TOOL_HOTPOTATOES:
7578
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7579
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7580
                break;
7581
            case TOOL_STUDENTPUBLICATION:
7582
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7583
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7584
                break;
7585
            case TOOL_FORUM:
7586
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7587
                $return .= $this->display_forum_form('edit', $item_id, $row);
7588
                break;
7589
            case TOOL_THREAD:
7590
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7591
                $return .= $this->display_thread_form('edit', $item_id, $row);
7592
                break;
7593
        }
7594
7595
        return $return;
7596
    }
7597
7598
    /**
7599
     * Function that displays a list with al the resources that
7600
     * could be added to the learning path.
7601
     *
7602
     * @throws Exception
7603
     * @throws HTML_QuickForm_Error
7604
     *
7605
     * @return bool
7606
     */
7607
    public function display_resources()
7608
    {
7609
        $course_code = api_get_course_id();
7610
7611
        // Get all the docs.
7612
        $documents = $this->get_documents(true);
7613
7614
        // Get all the exercises.
7615
        $exercises = $this->get_exercises();
7616
7617
        // Get all the links.
7618
        $links = $this->get_links();
7619
7620
        // Get all the student publications.
7621
        $works = $this->get_student_publications();
7622
7623
        // Get all the forums.
7624
        $forums = $this->get_forums(null, $course_code);
7625
7626
        // Get the final item form (see BT#11048) .
7627
        $finish = $this->getFinalItemForm();
7628
7629
        $headers = [
7630
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7631
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7632
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7633
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7634
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7635
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7636
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7637
        ];
7638
7639
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7640
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7641
        echo Display::tabs(
7642
            $headers,
7643
            [
7644
                $documents,
7645
                $exercises,
7646
                $links,
7647
                $works,
7648
                $forums,
7649
                $dir,
7650
                $finish,
7651
            ],
7652
            'resource_tab'
7653
        );
7654
7655
        return true;
7656
    }
7657
7658
    /**
7659
     * Returns the extension of a document.
7660
     *
7661
     * @param string $filename
7662
     *
7663
     * @return string Extension (part after the last dot)
7664
     */
7665
    public function get_extension($filename)
7666
    {
7667
        $explode = explode('.', $filename);
7668
7669
        return $explode[count($explode) - 1];
7670
    }
7671
7672
    /**
7673
     * Displays a document by id.
7674
     *
7675
     * @param int  $id
7676
     * @param bool $show_title
7677
     * @param bool $iframe
7678
     * @param bool $edit_link
7679
     *
7680
     * @return string
7681
     */
7682
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7683
    {
7684
        $_course = api_get_course_info();
7685
        $course_id = api_get_course_int_id();
7686
        $id = (int) $id;
7687
        $return = '';
7688
        $table = Database::get_course_table(TABLE_DOCUMENT);
7689
        $sql_doc = "SELECT * FROM $table
7690
                    WHERE c_id = $course_id AND iid = $id";
7691
        $res_doc = Database::query($sql_doc);
7692
        $row_doc = Database::fetch_array($res_doc);
7693
7694
        // TODO: Add a path filter.
7695
        if ($iframe) {
7696
            $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>';
7697
        } else {
7698
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7699
        }
7700
7701
        return $return;
7702
    }
7703
7704
    /**
7705
     * Return HTML form to add/edit a quiz.
7706
     *
7707
     * @param string $action     Action (add/edit)
7708
     * @param int    $id         Item ID if already exists
7709
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7710
     *
7711
     * @throws Exception
7712
     *
7713
     * @return string HTML form
7714
     */
7715
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7716
    {
7717
        $course_id = api_get_course_int_id();
7718
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7719
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7720
7721
        if ($id != 0 && is_array($extra_info)) {
7722
            $item_title = $extra_info['title'];
7723
            $item_description = $extra_info['description'];
7724
        } elseif (is_numeric($extra_info)) {
7725
            $sql = "SELECT title, description
7726
                    FROM $tbl_quiz
7727
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7728
7729
            $result = Database::query($sql);
7730
            $row = Database::fetch_array($result);
7731
            $item_title = $row['title'];
7732
            $item_description = $row['description'];
7733
        } else {
7734
            $item_title = '';
7735
            $item_description = '';
7736
        }
7737
        $item_title = Security::remove_XSS($item_title);
7738
        $item_description = Security::remove_XSS($item_description);
7739
7740
        if ($id != 0 && is_array($extra_info)) {
7741
            $parent = $extra_info['parent_item_id'];
7742
        } else {
7743
            $parent = 0;
7744
        }
7745
7746
        $sql = "SELECT * FROM $tbl_lp_item 
7747
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7748
7749
        $result = Database::query($sql);
7750
        $arrLP = [];
7751
        while ($row = Database::fetch_array($result)) {
7752
            $arrLP[] = [
7753
                'id' => $row['iid'],
7754
                'item_type' => $row['item_type'],
7755
                'title' => $row['title'],
7756
                'path' => $row['path'],
7757
                'description' => $row['description'],
7758
                'parent_item_id' => $row['parent_item_id'],
7759
                'previous_item_id' => $row['previous_item_id'],
7760
                'next_item_id' => $row['next_item_id'],
7761
                'display_order' => $row['display_order'],
7762
                'max_score' => $row['max_score'],
7763
                'min_score' => $row['min_score'],
7764
                'mastery_score' => $row['mastery_score'],
7765
                'prerequisite' => $row['prerequisite'],
7766
                'max_time_allowed' => $row['max_time_allowed'],
7767
            ];
7768
        }
7769
7770
        $this->tree_array($arrLP);
7771
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7772
        unset($this->arrMenu);
7773
7774
        $form = new FormValidator(
7775
            'quiz_form',
7776
            'POST',
7777
            $this->getCurrentBuildingModeURL()
7778
        );
7779
        $defaults = [];
7780
7781
        if ($action == 'add') {
7782
            $legend = get_lang('CreateTheExercise');
7783
        } elseif ($action == 'move') {
7784
            $legend = get_lang('MoveTheCurrentExercise');
7785
        } else {
7786
            $legend = get_lang('EditCurrentExecice');
7787
        }
7788
7789
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7790
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7791
        }
7792
7793
        $form->addHeader($legend);
7794
7795
        if ($action != 'move') {
7796
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7797
            $defaults['title'] = $item_title;
7798
        }
7799
7800
        // Select for Parent item, root or chapter
7801
        $selectParent = $form->addSelect(
7802
            'parent',
7803
            get_lang('Parent'),
7804
            [],
7805
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7806
        );
7807
        $selectParent->addOption($this->name, 0);
7808
7809
        $arrHide = [
7810
            $id,
7811
        ];
7812
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7813
            if ($action != 'add') {
7814
                if (
7815
                    ($arrLP[$i]['item_type'] == 'dir') &&
7816
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7817
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7818
                ) {
7819
                    $selectParent->addOption(
7820
                        $arrLP[$i]['title'],
7821
                        $arrLP[$i]['id'],
7822
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7823
                    );
7824
7825
                    if ($parent == $arrLP[$i]['id']) {
7826
                        $selectParent->setSelected($arrLP[$i]['id']);
7827
                    }
7828
                } else {
7829
                    $arrHide[] = $arrLP[$i]['id'];
7830
                }
7831
            } else {
7832
                if ($arrLP[$i]['item_type'] == 'dir') {
7833
                    $selectParent->addOption(
7834
                        $arrLP[$i]['title'],
7835
                        $arrLP[$i]['id'],
7836
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7837
                    );
7838
7839
                    if ($parent == $arrLP[$i]['id']) {
7840
                        $selectParent->setSelected($arrLP[$i]['id']);
7841
                    }
7842
                }
7843
            }
7844
        }
7845
        if (is_array($arrLP)) {
7846
            reset($arrLP);
7847
        }
7848
7849
        $selectPrevious = $form->addSelect(
7850
            'previous',
7851
            get_lang('Position'),
7852
            [],
7853
            ['id' => 'previous']
7854
        );
7855
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7856
7857
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7858
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7859
                $arrLP[$i]['id'] != $id
7860
            ) {
7861
                $selectPrevious->addOption(
7862
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7863
                    $arrLP[$i]['id']
7864
                );
7865
7866
                if (is_array($extra_info)) {
7867
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7868
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7869
                    }
7870
                } elseif ($action == 'add') {
7871
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7872
                }
7873
            }
7874
        }
7875
7876
        if ($action != 'move') {
7877
            $arrHide = [];
7878
            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...
7879
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7880
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7881
                }
7882
            }
7883
        }
7884
7885
        if ($action == 'add') {
7886
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7887
        } else {
7888
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7889
        }
7890
7891
        if ($action == 'move') {
7892
            $form->addHidden('title', $item_title);
7893
            $form->addHidden('description', $item_description);
7894
        }
7895
7896
        if (is_numeric($extra_info)) {
7897
            $form->addHidden('path', $extra_info);
7898
        } elseif (is_array($extra_info)) {
7899
            $form->addHidden('path', $extra_info['path']);
7900
        }
7901
7902
        $form->addHidden('type', TOOL_QUIZ);
7903
        $form->addHidden('post_time', time());
7904
        $form->setDefaults($defaults);
7905
7906
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7907
    }
7908
7909
    /**
7910
     * Addition of Hotpotatoes tests.
7911
     *
7912
     * @param string $action
7913
     * @param int    $id         Internal ID of the item
7914
     * @param string $extra_info
7915
     *
7916
     * @return string HTML structure to display the hotpotatoes addition formular
7917
     */
7918
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7919
    {
7920
        $course_id = api_get_course_int_id();
7921
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7922
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7923
7924
        if ($id != 0 && is_array($extra_info)) {
7925
            $item_title = stripslashes($extra_info['title']);
7926
            $item_description = stripslashes($extra_info['description']);
7927
        } elseif (is_numeric($extra_info)) {
7928
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7929
7930
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7931
                    WHERE
7932
                        c_id = ".$course_id." AND
7933
                        path LIKE '".$uploadPath."/%/%htm%' AND
7934
                        iid = ".(int) $extra_info."
7935
                    ORDER BY iid ASC";
7936
7937
            $res_hot = Database::query($sql);
7938
            $row = Database::fetch_array($res_hot);
7939
7940
            $item_title = $row['title'];
7941
            $item_description = $row['description'];
7942
7943
            if (!empty($row['comment'])) {
7944
                $item_title = $row['comment'];
7945
            }
7946
        } else {
7947
            $item_title = '';
7948
            $item_description = '';
7949
        }
7950
7951
        if ($id != 0 && is_array($extra_info)) {
7952
            $parent = $extra_info['parent_item_id'];
7953
        } else {
7954
            $parent = 0;
7955
        }
7956
7957
        $sql = "SELECT * FROM $tbl_lp_item
7958
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7959
        $result = Database::query($sql);
7960
        $arrLP = [];
7961
        while ($row = Database::fetch_array($result)) {
7962
            $arrLP[] = [
7963
                'id' => $row['id'],
7964
                'item_type' => $row['item_type'],
7965
                'title' => $row['title'],
7966
                'path' => $row['path'],
7967
                'description' => $row['description'],
7968
                'parent_item_id' => $row['parent_item_id'],
7969
                'previous_item_id' => $row['previous_item_id'],
7970
                'next_item_id' => $row['next_item_id'],
7971
                'display_order' => $row['display_order'],
7972
                'max_score' => $row['max_score'],
7973
                'min_score' => $row['min_score'],
7974
                'mastery_score' => $row['mastery_score'],
7975
                'prerequisite' => $row['prerequisite'],
7976
                'max_time_allowed' => $row['max_time_allowed'],
7977
            ];
7978
        }
7979
7980
        $legend = '<legend>';
7981
        if ($action == 'add') {
7982
            $legend .= get_lang('CreateTheExercise');
7983
        } elseif ($action == 'move') {
7984
            $legend .= get_lang('MoveTheCurrentExercise');
7985
        } else {
7986
            $legend .= get_lang('EditCurrentExecice');
7987
        }
7988
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7989
            $legend .= Display:: return_message(
7990
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7991
            );
7992
        }
7993
        $legend .= '</legend>';
7994
7995
        $return = '<form method="POST">';
7996
        $return .= $legend;
7997
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7998
        $return .= '<tr>';
7999
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
8000
        $return .= '<td class="input">';
8001
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
8002
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
8003
        $arrHide = [
8004
            $id,
8005
        ];
8006
8007
        if (count($arrLP) > 0) {
8008
            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...
8009
                if ($action != 'add') {
8010
                    if ($arrLP[$i]['item_type'] == 'dir' &&
8011
                        !in_array($arrLP[$i]['id'], $arrHide) &&
8012
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8013
                    ) {
8014
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8015
                    } else {
8016
                        $arrHide[] = $arrLP[$i]['id'];
8017
                    }
8018
                } else {
8019
                    if ($arrLP[$i]['item_type'] == 'dir') {
8020
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8021
                    }
8022
                }
8023
            }
8024
            reset($arrLP);
8025
        }
8026
8027
        $return .= '</select>';
8028
        $return .= '</td>';
8029
        $return .= '</tr>';
8030
        $return .= '<tr>';
8031
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
8032
        $return .= '<td class="input">';
8033
        $return .= '<select id="previous" name="previous" size="1">';
8034
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
8035
8036
        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...
8037
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8038
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8039
                    $selected = 'selected="selected" ';
8040
                } elseif ($action == 'add') {
8041
                    $selected = 'selected="selected" ';
8042
                } else {
8043
                    $selected = '';
8044
                }
8045
8046
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
8047
            }
8048
        }
8049
8050
        $return .= '</select>';
8051
        $return .= '</td>';
8052
        $return .= '</tr>';
8053
8054
        if ($action != 'move') {
8055
            $return .= '<tr>';
8056
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8057
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8058
            $return .= '</tr>';
8059
            $id_prerequisite = 0;
8060
            if (is_array($arrLP) && count($arrLP) > 0) {
8061
                foreach ($arrLP as $key => $value) {
8062
                    if ($value['id'] == $id) {
8063
                        $id_prerequisite = $value['prerequisite'];
8064
                        break;
8065
                    }
8066
                }
8067
8068
                $arrHide = [];
8069
                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...
8070
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8071
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8072
                    }
8073
                }
8074
            }
8075
        }
8076
8077
        $return .= '<tr>';
8078
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8079
            get_lang('SaveHotpotatoes').'</button></td>';
8080
        $return .= '</tr>';
8081
        $return .= '</table>';
8082
8083
        if ($action == 'move') {
8084
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8085
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8086
        }
8087
8088
        if (is_numeric($extra_info)) {
8089
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8090
        } elseif (is_array($extra_info)) {
8091
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8092
        }
8093
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8094
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8095
        $return .= '</form>';
8096
8097
        return $return;
8098
    }
8099
8100
    /**
8101
     * Return the form to display the forum edit/add option.
8102
     *
8103
     * @param string $action
8104
     * @param int    $id         ID of the lp_item if already exists
8105
     * @param string $extra_info
8106
     *
8107
     * @throws Exception
8108
     *
8109
     * @return string HTML form
8110
     */
8111
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
8112
    {
8113
        $course_id = api_get_course_int_id();
8114
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8115
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8116
8117
        if ($id != 0 && is_array($extra_info)) {
8118
            $item_title = stripslashes($extra_info['title']);
8119
        } elseif (is_numeric($extra_info)) {
8120
            $sql = "SELECT forum_title as title, forum_comment as comment
8121
                    FROM $tbl_forum
8122
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8123
8124
            $result = Database::query($sql);
8125
            $row = Database::fetch_array($result);
8126
8127
            $item_title = $row['title'];
8128
            $item_description = $row['comment'];
8129
        } else {
8130
            $item_title = '';
8131
            $item_description = '';
8132
        }
8133
8134
        if ($id != 0 && is_array($extra_info)) {
8135
            $parent = $extra_info['parent_item_id'];
8136
        } else {
8137
            $parent = 0;
8138
        }
8139
8140
        $sql = "SELECT * FROM $tbl_lp_item
8141
                WHERE
8142
                    c_id = $course_id AND
8143
                    lp_id = ".$this->lp_id;
8144
        $result = Database::query($sql);
8145
        $arrLP = [];
8146
        while ($row = Database::fetch_array($result)) {
8147
            $arrLP[] = [
8148
                'id' => $row['iid'],
8149
                'item_type' => $row['item_type'],
8150
                'title' => $row['title'],
8151
                'path' => $row['path'],
8152
                'description' => $row['description'],
8153
                'parent_item_id' => $row['parent_item_id'],
8154
                'previous_item_id' => $row['previous_item_id'],
8155
                'next_item_id' => $row['next_item_id'],
8156
                'display_order' => $row['display_order'],
8157
                'max_score' => $row['max_score'],
8158
                'min_score' => $row['min_score'],
8159
                'mastery_score' => $row['mastery_score'],
8160
                'prerequisite' => $row['prerequisite'],
8161
            ];
8162
        }
8163
8164
        $this->tree_array($arrLP);
8165
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8166
        unset($this->arrMenu);
8167
8168
        if ($action == 'add') {
8169
            $legend = get_lang('CreateTheForum');
8170
        } elseif ($action == 'move') {
8171
            $legend = get_lang('MoveTheCurrentForum');
8172
        } else {
8173
            $legend = get_lang('EditCurrentForum');
8174
        }
8175
8176
        $form = new FormValidator(
8177
            'forum_form',
8178
            'POST',
8179
            $this->getCurrentBuildingModeURL()
8180
        );
8181
        $defaults = [];
8182
8183
        $form->addHeader($legend);
8184
8185
        if ($action != 'move') {
8186
            $form->addText(
8187
                'title',
8188
                get_lang('Title'),
8189
                true,
8190
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8191
            );
8192
            $defaults['title'] = $item_title;
8193
        }
8194
8195
        $selectParent = $form->addSelect(
8196
            'parent',
8197
            get_lang('Parent'),
8198
            [],
8199
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8200
        );
8201
        $selectParent->addOption($this->name, 0);
8202
        $arrHide = [
8203
            $id,
8204
        ];
8205
        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...
8206
            if ($action != 'add') {
8207
                if ($arrLP[$i]['item_type'] == 'dir' &&
8208
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8209
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8210
                ) {
8211
                    $selectParent->addOption(
8212
                        $arrLP[$i]['title'],
8213
                        $arrLP[$i]['id'],
8214
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8215
                    );
8216
8217
                    if ($parent == $arrLP[$i]['id']) {
8218
                        $selectParent->setSelected($arrLP[$i]['id']);
8219
                    }
8220
                } else {
8221
                    $arrHide[] = $arrLP[$i]['id'];
8222
                }
8223
            } else {
8224
                if ($arrLP[$i]['item_type'] == 'dir') {
8225
                    $selectParent->addOption(
8226
                        $arrLP[$i]['title'],
8227
                        $arrLP[$i]['id'],
8228
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8229
                    );
8230
8231
                    if ($parent == $arrLP[$i]['id']) {
8232
                        $selectParent->setSelected($arrLP[$i]['id']);
8233
                    }
8234
                }
8235
            }
8236
        }
8237
8238
        if (is_array($arrLP)) {
8239
            reset($arrLP);
8240
        }
8241
8242
        $selectPrevious = $form->addSelect(
8243
            'previous',
8244
            get_lang('Position'),
8245
            [],
8246
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8247
        );
8248
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8249
8250
        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...
8251
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8252
                $arrLP[$i]['id'] != $id
8253
            ) {
8254
                $selectPrevious->addOption(
8255
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8256
                    $arrLP[$i]['id']
8257
                );
8258
8259
                if (isset($extra_info['previous_item_id']) &&
8260
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8261
                ) {
8262
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8263
                } elseif ($action == 'add') {
8264
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8265
                }
8266
            }
8267
        }
8268
8269
        if ($action != 'move') {
8270
            $id_prerequisite = 0;
8271
            if (is_array($arrLP)) {
8272
                foreach ($arrLP as $key => $value) {
8273
                    if ($value['id'] == $id) {
8274
                        $id_prerequisite = $value['prerequisite'];
8275
                        break;
8276
                    }
8277
                }
8278
            }
8279
8280
            $arrHide = [];
8281
            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...
8282
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8283
                    if (isset($extra_info['previous_item_id']) &&
8284
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8285
                    ) {
8286
                        $s_selected_position = $arrLP[$i]['id'];
8287
                    } elseif ($action == 'add') {
8288
                        $s_selected_position = 0;
8289
                    }
8290
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8291
                }
8292
            }
8293
        }
8294
8295
        if ($action == 'add') {
8296
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8297
        } else {
8298
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8299
        }
8300
8301
        if ($action == 'move') {
8302
            $form->addHidden('title', $item_title);
8303
            $form->addHidden('description', $item_description);
8304
        }
8305
8306
        if (is_numeric($extra_info)) {
8307
            $form->addHidden('path', $extra_info);
8308
        } elseif (is_array($extra_info)) {
8309
            $form->addHidden('path', $extra_info['path']);
8310
        }
8311
        $form->addHidden('type', TOOL_FORUM);
8312
        $form->addHidden('post_time', time());
8313
        $form->setDefaults($defaults);
8314
8315
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8316
    }
8317
8318
    /**
8319
     * Return HTML form to add/edit forum threads.
8320
     *
8321
     * @param string $action
8322
     * @param int    $id         Item ID if already exists in learning path
8323
     * @param string $extra_info
8324
     *
8325
     * @throws Exception
8326
     *
8327
     * @return string HTML form
8328
     */
8329
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8330
    {
8331
        $course_id = api_get_course_int_id();
8332
        if (empty($course_id)) {
8333
            return null;
8334
        }
8335
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8336
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8337
8338
        if ($id != 0 && is_array($extra_info)) {
8339
            $item_title = stripslashes($extra_info['title']);
8340
        } elseif (is_numeric($extra_info)) {
8341
            $sql = "SELECT thread_title as title FROM $tbl_forum
8342
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8343
8344
            $result = Database::query($sql);
8345
            $row = Database::fetch_array($result);
8346
8347
            $item_title = $row['title'];
8348
            $item_description = '';
8349
        } else {
8350
            $item_title = '';
8351
            $item_description = '';
8352
        }
8353
8354
        if ($id != 0 && is_array($extra_info)) {
8355
            $parent = $extra_info['parent_item_id'];
8356
        } else {
8357
            $parent = 0;
8358
        }
8359
8360
        $sql = "SELECT * FROM $tbl_lp_item
8361
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8362
        $result = Database::query($sql);
8363
8364
        $arrLP = [];
8365
        while ($row = Database::fetch_array($result)) {
8366
            $arrLP[] = [
8367
                'id' => $row['iid'],
8368
                'item_type' => $row['item_type'],
8369
                'title' => $row['title'],
8370
                'path' => $row['path'],
8371
                'description' => $row['description'],
8372
                'parent_item_id' => $row['parent_item_id'],
8373
                'previous_item_id' => $row['previous_item_id'],
8374
                'next_item_id' => $row['next_item_id'],
8375
                'display_order' => $row['display_order'],
8376
                'max_score' => $row['max_score'],
8377
                'min_score' => $row['min_score'],
8378
                'mastery_score' => $row['mastery_score'],
8379
                'prerequisite' => $row['prerequisite'],
8380
            ];
8381
        }
8382
8383
        $this->tree_array($arrLP);
8384
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8385
        unset($this->arrMenu);
8386
8387
        $form = new FormValidator(
8388
            'thread_form',
8389
            'POST',
8390
            $this->getCurrentBuildingModeURL()
8391
        );
8392
        $defaults = [];
8393
8394
        if ($action == 'add') {
8395
            $legend = get_lang('CreateTheForum');
8396
        } elseif ($action == 'move') {
8397
            $legend = get_lang('MoveTheCurrentForum');
8398
        } else {
8399
            $legend = get_lang('EditCurrentForum');
8400
        }
8401
8402
        $form->addHeader($legend);
8403
        $selectParent = $form->addSelect(
8404
            'parent',
8405
            get_lang('Parent'),
8406
            [],
8407
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8408
        );
8409
        $selectParent->addOption($this->name, 0);
8410
8411
        $arrHide = [
8412
            $id,
8413
        ];
8414
8415
        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...
8416
            if ($action != 'add') {
8417
                if (
8418
                    ($arrLP[$i]['item_type'] == 'dir') &&
8419
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8420
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8421
                ) {
8422
                    $selectParent->addOption(
8423
                        $arrLP[$i]['title'],
8424
                        $arrLP[$i]['id'],
8425
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8426
                    );
8427
8428
                    if ($parent == $arrLP[$i]['id']) {
8429
                        $selectParent->setSelected($arrLP[$i]['id']);
8430
                    }
8431
                } else {
8432
                    $arrHide[] = $arrLP[$i]['id'];
8433
                }
8434
            } else {
8435
                if ($arrLP[$i]['item_type'] == 'dir') {
8436
                    $selectParent->addOption(
8437
                        $arrLP[$i]['title'],
8438
                        $arrLP[$i]['id'],
8439
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8440
                    );
8441
8442
                    if ($parent == $arrLP[$i]['id']) {
8443
                        $selectParent->setSelected($arrLP[$i]['id']);
8444
                    }
8445
                }
8446
            }
8447
        }
8448
8449
        if ($arrLP != null) {
8450
            reset($arrLP);
8451
        }
8452
8453
        $selectPrevious = $form->addSelect(
8454
            'previous',
8455
            get_lang('Position'),
8456
            [],
8457
            ['id' => 'previous']
8458
        );
8459
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8460
8461
        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...
8462
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8463
                $selectPrevious->addOption(
8464
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8465
                    $arrLP[$i]['id']
8466
                );
8467
8468
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8469
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8470
                } elseif ($action == 'add') {
8471
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8472
                }
8473
            }
8474
        }
8475
8476
        if ($action != 'move') {
8477
            $form->addText(
8478
                'title',
8479
                get_lang('Title'),
8480
                true,
8481
                ['id' => 'idTitle']
8482
            );
8483
            $defaults['title'] = $item_title;
8484
8485
            $id_prerequisite = 0;
8486
            if ($arrLP != null) {
8487
                foreach ($arrLP as $key => $value) {
8488
                    if ($value['id'] == $id) {
8489
                        $id_prerequisite = $value['prerequisite'];
8490
                        break;
8491
                    }
8492
                }
8493
            }
8494
8495
            $arrHide = [];
8496
            $s_selected_position = 0;
8497
            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...
8498
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8499
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8500
                        $s_selected_position = $arrLP[$i]['id'];
8501
                    } elseif ($action == 'add') {
8502
                        $s_selected_position = 0;
8503
                    }
8504
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8505
                }
8506
            }
8507
8508
            $selectPrerequisites = $form->addSelect(
8509
                'prerequisites',
8510
                get_lang('LearnpathPrerequisites'),
8511
                [],
8512
                ['id' => 'prerequisites']
8513
            );
8514
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8515
8516
            foreach ($arrHide as $key => $value) {
8517
                $selectPrerequisites->addOption($value['value'], $key);
8518
8519
                if ($key == $s_selected_position && $action == 'add') {
8520
                    $selectPrerequisites->setSelected($key);
8521
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8522
                    $selectPrerequisites->setSelected($key);
8523
                }
8524
            }
8525
        }
8526
8527
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8528
8529
        if ($action == 'move') {
8530
            $form->addHidden('title', $item_title);
8531
            $form->addHidden('description', $item_description);
8532
        }
8533
8534
        if (is_numeric($extra_info)) {
8535
            $form->addHidden('path', $extra_info);
8536
        } elseif (is_array($extra_info)) {
8537
            $form->addHidden('path', $extra_info['path']);
8538
        }
8539
8540
        $form->addHidden('type', TOOL_THREAD);
8541
        $form->addHidden('post_time', time());
8542
        $form->setDefaults($defaults);
8543
8544
        return $form->returnForm();
8545
    }
8546
8547
    /**
8548
     * Return the HTML form to display an item (generally a dir item).
8549
     *
8550
     * @param string $item_type
8551
     * @param string $title
8552
     * @param string $action
8553
     * @param int    $id
8554
     * @param string $extra_info
8555
     *
8556
     * @throws Exception
8557
     * @throws HTML_QuickForm_Error
8558
     *
8559
     * @return string HTML form
8560
     */
8561
    public function display_item_form(
8562
        $item_type,
8563
        $title = '',
8564
        $action = 'add_item',
8565
        $id = 0,
8566
        $extra_info = 'new'
8567
    ) {
8568
        $_course = api_get_course_info();
8569
8570
        global $charset;
8571
8572
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8573
        $item_title = '';
8574
        $item_description = '';
8575
        $item_path_fck = '';
8576
8577
        if ($id != 0 && is_array($extra_info)) {
8578
            $item_title = $extra_info['title'];
8579
            $item_description = $extra_info['description'];
8580
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8581
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8582
        }
8583
        $parent = 0;
8584
        if ($id != 0 && is_array($extra_info)) {
8585
            $parent = $extra_info['parent_item_id'];
8586
        }
8587
8588
        $id = (int) $id;
8589
        $sql = "SELECT * FROM $tbl_lp_item
8590
                WHERE
8591
                    lp_id = ".$this->lp_id." AND
8592
                    iid != $id";
8593
8594
        if ($item_type == 'dir') {
8595
            $sql .= " AND parent_item_id = 0";
8596
        }
8597
8598
        $result = Database::query($sql);
8599
        $arrLP = [];
8600
        while ($row = Database::fetch_array($result)) {
8601
            $arrLP[] = [
8602
                'id' => $row['iid'],
8603
                'item_type' => $row['item_type'],
8604
                'title' => $row['title'],
8605
                'path' => $row['path'],
8606
                'description' => $row['description'],
8607
                'parent_item_id' => $row['parent_item_id'],
8608
                'previous_item_id' => $row['previous_item_id'],
8609
                'next_item_id' => $row['next_item_id'],
8610
                'max_score' => $row['max_score'],
8611
                'min_score' => $row['min_score'],
8612
                'mastery_score' => $row['mastery_score'],
8613
                'prerequisite' => $row['prerequisite'],
8614
                'display_order' => $row['display_order'],
8615
            ];
8616
        }
8617
8618
        $this->tree_array($arrLP);
8619
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8620
        unset($this->arrMenu);
8621
8622
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8623
8624
        $form = new FormValidator('form', 'POST', $url);
8625
        $defaults['title'] = api_html_entity_decode(
8626
            $item_title,
8627
            ENT_QUOTES,
8628
            $charset
8629
        );
8630
        $defaults['description'] = $item_description;
8631
8632
        $form->addHeader($title);
8633
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8634
        $arrHide[0]['padding'] = 20;
8635
        $charset = api_get_system_encoding();
8636
        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...
8637
            if ($action != 'add') {
8638
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8639
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8640
                ) {
8641
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8642
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8643
                    if ($parent == $arrLP[$i]['id']) {
8644
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8645
                    }
8646
                }
8647
            } else {
8648
                if ($arrLP[$i]['item_type'] == 'dir') {
8649
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8650
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8651
                    if ($parent == $arrLP[$i]['id']) {
8652
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8653
                    }
8654
                }
8655
            }
8656
        }
8657
8658
        if ($action != 'move') {
8659
            $form->addElement('text', 'title', get_lang('Title'));
8660
            $form->applyFilter('title', 'html_filter');
8661
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8662
        } else {
8663
            $form->addElement('hidden', 'title');
8664
        }
8665
8666
        $parentSelect = $form->addElement(
8667
            'select',
8668
            'parent',
8669
            get_lang('Parent'),
8670
            '',
8671
            [
8672
                'id' => 'idParent',
8673
                'onchange' => "javascript: load_cbo(this.value);",
8674
            ]
8675
        );
8676
8677
        foreach ($arrHide as $key => $value) {
8678
            $parentSelect->addOption(
8679
                $value['value'],
8680
                $key,
8681
                'style="padding-left:'.$value['padding'].'px;"'
8682
            );
8683
            $lastPosition = $key;
8684
        }
8685
8686
        if (!empty($s_selected_parent)) {
8687
            $parentSelect->setSelected($s_selected_parent);
8688
        }
8689
8690
        if (is_array($arrLP)) {
8691
            reset($arrLP);
8692
        }
8693
        $arrHide = [];
8694
        // POSITION
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]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8697
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8698
                //this is the same!
8699
                if (isset($extra_info['previous_item_id']) &&
8700
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8701
                ) {
8702
                    $s_selected_position = $arrLP[$i]['id'];
8703
                } elseif ($action == 'add') {
8704
                    $s_selected_position = $arrLP[$i]['id'];
8705
                }
8706
8707
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8708
            }
8709
        }
8710
8711
        $position = $form->addElement(
8712
            'select',
8713
            'previous',
8714
            get_lang('Position'),
8715
            '',
8716
            ['id' => 'previous']
8717
        );
8718
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8719
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8720
8721
        $lastPosition = null;
8722
        foreach ($arrHide as $key => $value) {
8723
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8724
            $lastPosition = $key;
8725
        }
8726
8727
        if (!empty($s_selected_position)) {
8728
            $position->setSelected($s_selected_position);
8729
        }
8730
8731
        // When new chapter add at the end
8732
        if ($action == 'add_item') {
8733
            $position->setSelected($lastPosition);
8734
        }
8735
8736
        if (is_array($arrLP)) {
8737
            reset($arrLP);
8738
        }
8739
8740
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8741
8742
        //fix in order to use the tab
8743
        if ($item_type == 'dir') {
8744
            $form->addElement('hidden', 'type', 'dir');
8745
        }
8746
8747
        $extension = null;
8748
        if (!empty($item_path)) {
8749
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8750
        }
8751
8752
        //assets can't be modified
8753
        //$item_type == 'asset' ||
8754
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8755
            if ($item_type == 'sco') {
8756
                $form->addElement(
8757
                    'html',
8758
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8759
                );
8760
            }
8761
            $renderer = $form->defaultRenderer();
8762
            $renderer->setElementTemplate(
8763
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8764
                'content_lp'
8765
            );
8766
8767
            $relative_prefix = '';
8768
8769
            $editor_config = [
8770
                'ToolbarSet' => 'LearningPathDocuments',
8771
                'Width' => '100%',
8772
                'Height' => '500',
8773
                'FullPage' => true,
8774
                'CreateDocumentDir' => $relative_prefix,
8775
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8776
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8777
            ];
8778
8779
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8780
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8781
            $defaults['content_lp'] = file_get_contents($content_path);
8782
        }
8783
8784
        if (!empty($id)) {
8785
            $form->addHidden('id', $id);
8786
        }
8787
8788
        $form->addElement('hidden', 'type', $item_type);
8789
        $form->addElement('hidden', 'post_time', time());
8790
        $form->setDefaults($defaults);
8791
8792
        return $form->returnForm();
8793
    }
8794
8795
    /**
8796
     * @return string
8797
     */
8798
    public function getCurrentBuildingModeURL()
8799
    {
8800
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8801
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8802
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8803
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8804
8805
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8806
8807
        return $currentUrl;
8808
    }
8809
8810
    /**
8811
     * Returns the form to update or create a document.
8812
     *
8813
     * @param string $action     (add/edit)
8814
     * @param int    $id         ID of the lp_item (if already exists)
8815
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8816
     *
8817
     * @throws Exception
8818
     * @throws HTML_QuickForm_Error
8819
     *
8820
     * @return string HTML form
8821
     */
8822
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8823
    {
8824
        $course_id = api_get_course_int_id();
8825
        $_course = api_get_course_info();
8826
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8827
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8828
8829
        $no_display_edit_textarea = false;
8830
        $item_description = '';
8831
        //If action==edit document
8832
        //We don't display the document form if it's not an editable document (html or txt file)
8833
        if ($action === 'edit') {
8834
            if (is_array($extra_info)) {
8835
                $path_parts = pathinfo($extra_info['dir']);
8836
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8837
                    $no_display_edit_textarea = true;
8838
                }
8839
            }
8840
        }
8841
        $no_display_add = false;
8842
8843
        // If action==add an existing document
8844
        // We don't display the document form if it's not an editable document (html or txt file).
8845
        if ($action === 'add') {
8846
            if (is_numeric($extra_info)) {
8847
                $extra_info = (int) $extra_info;
8848
                $sql_doc = "SELECT path FROM $tbl_doc 
8849
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8850
                $result = Database::query($sql_doc);
8851
                $path_file = Database::result($result, 0, 0);
8852
                $path_parts = pathinfo($path_file);
8853
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8854
                    $no_display_add = true;
8855
                }
8856
            }
8857
        }
8858
        if ($id != 0 && is_array($extra_info)) {
8859
            $item_title = stripslashes($extra_info['title']);
8860
            $item_description = stripslashes($extra_info['description']);
8861
            if (empty($item_title)) {
8862
                $path_parts = pathinfo($extra_info['path']);
8863
                $item_title = stripslashes($path_parts['filename']);
8864
            }
8865
        } elseif (is_numeric($extra_info)) {
8866
            $sql = "SELECT path, title FROM $tbl_doc
8867
                    WHERE
8868
                        c_id = ".$course_id." AND
8869
                        iid = ".intval($extra_info);
8870
            $result = Database::query($sql);
8871
            $row = Database::fetch_array($result);
8872
            $item_title = $row['title'];
8873
            $item_title = str_replace('_', ' ', $item_title);
8874
            if (empty($item_title)) {
8875
                $path_parts = pathinfo($row['path']);
8876
                $item_title = stripslashes($path_parts['filename']);
8877
            }
8878
        } else {
8879
            $item_title = '';
8880
            $item_description = '';
8881
        }
8882
        $return = '<legend>';
8883
        $parent = 0;
8884
        if ($id != 0 && is_array($extra_info)) {
8885
            $parent = $extra_info['parent_item_id'];
8886
        }
8887
8888
        $sql = "SELECT * FROM $tbl_lp_item
8889
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8890
        $result = Database::query($sql);
8891
        $arrLP = [];
8892
8893
        while ($row = Database::fetch_array($result)) {
8894
            $arrLP[] = [
8895
                'id' => $row['iid'],
8896
                'item_type' => $row['item_type'],
8897
                'title' => $row['title'],
8898
                'path' => $row['path'],
8899
                'description' => $row['description'],
8900
                'parent_item_id' => $row['parent_item_id'],
8901
                'previous_item_id' => $row['previous_item_id'],
8902
                'next_item_id' => $row['next_item_id'],
8903
                'display_order' => $row['display_order'],
8904
                'max_score' => $row['max_score'],
8905
                'min_score' => $row['min_score'],
8906
                'mastery_score' => $row['mastery_score'],
8907
                'prerequisite' => $row['prerequisite'],
8908
            ];
8909
        }
8910
8911
        $this->tree_array($arrLP);
8912
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8913
        unset($this->arrMenu);
8914
8915
        if ($action == 'add') {
8916
            $return .= get_lang('CreateTheDocument');
8917
        } elseif ($action == 'move') {
8918
            $return .= get_lang('MoveTheCurrentDocument');
8919
        } else {
8920
            $return .= get_lang('EditTheCurrentDocument');
8921
        }
8922
        $return .= '</legend>';
8923
8924
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8925
            $return .= Display::return_message(
8926
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8927
                false
8928
            );
8929
        }
8930
        $form = new FormValidator(
8931
            'form',
8932
            'POST',
8933
            $this->getCurrentBuildingModeURL(),
8934
            '',
8935
            ['enctype' => 'multipart/form-data']
8936
        );
8937
        $defaults['title'] = Security::remove_XSS($item_title);
8938
        if (empty($item_title)) {
8939
            $defaults['title'] = Security::remove_XSS($item_title);
8940
        }
8941
        $defaults['description'] = $item_description;
8942
        $form->addElement('html', $return);
8943
8944
        if ($action != 'move') {
8945
            $data = $this->generate_lp_folder($_course);
8946
            if ($action != 'edit') {
8947
                $folders = DocumentManager::get_all_document_folders(
8948
                    $_course,
8949
                    0,
8950
                    true
8951
                );
8952
                DocumentManager::build_directory_selector(
8953
                    $folders,
8954
                    '',
8955
                    [],
8956
                    true,
8957
                    $form,
8958
                    'directory_parent_id'
8959
                );
8960
            }
8961
8962
            if (isset($data['id'])) {
8963
                $defaults['directory_parent_id'] = $data['id'];
8964
            }
8965
8966
            $form->addElement(
8967
                'text',
8968
                'title',
8969
                get_lang('Title'),
8970
                ['id' => 'idTitle', 'class' => 'col-md-4']
8971
            );
8972
            $form->applyFilter('title', 'html_filter');
8973
        }
8974
8975
        $arrHide[0]['value'] = $this->name;
8976
        $arrHide[0]['padding'] = 20;
8977
8978
        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...
8979
            if ($action != 'add') {
8980
                if ($arrLP[$i]['item_type'] == 'dir' &&
8981
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8982
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8983
                ) {
8984
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8985
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8986
                }
8987
            } else {
8988
                if ($arrLP[$i]['item_type'] == 'dir') {
8989
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8990
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8991
                }
8992
            }
8993
        }
8994
8995
        $parentSelect = $form->addSelect(
8996
            'parent',
8997
            get_lang('Parent'),
8998
            [],
8999
            [
9000
                'id' => 'idParent',
9001
                'onchange' => 'javascript: load_cbo(this.value);',
9002
            ]
9003
        );
9004
9005
        $my_count = 0;
9006
        foreach ($arrHide as $key => $value) {
9007
            if ($my_count != 0) {
9008
                // The LP name is also the first section and is not in the same charset like the other sections.
9009
                $value['value'] = Security::remove_XSS($value['value']);
9010
                $parentSelect->addOption(
9011
                    $value['value'],
9012
                    $key,
9013
                    'style="padding-left:'.$value['padding'].'px;"'
9014
                );
9015
            } else {
9016
                $value['value'] = Security::remove_XSS($value['value']);
9017
                $parentSelect->addOption(
9018
                    $value['value'],
9019
                    $key,
9020
                    'style="padding-left:'.$value['padding'].'px;"'
9021
                );
9022
            }
9023
            $my_count++;
9024
        }
9025
9026
        if (!empty($id)) {
9027
            $parentSelect->setSelected($parent);
9028
        } else {
9029
            $parent_item_id = Session::read('parent_item_id', 0);
9030
            $parentSelect->setSelected($parent_item_id);
9031
        }
9032
9033
        if (is_array($arrLP)) {
9034
            reset($arrLP);
9035
        }
9036
9037
        $arrHide = [];
9038
        // POSITION
9039
        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...
9040
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9041
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9042
            ) {
9043
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9044
            }
9045
        }
9046
9047
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
9048
9049
        $position = $form->addSelect(
9050
            'previous',
9051
            get_lang('Position'),
9052
            [],
9053
            ['id' => 'previous']
9054
        );
9055
9056
        $position->addOption(get_lang('FirstPosition'), 0);
9057
        foreach ($arrHide as $key => $value) {
9058
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9059
            $position->addOption(
9060
                $value['value'],
9061
                $key,
9062
                'style="padding-left:'.$padding.'px;"'
9063
            );
9064
        }
9065
9066
        $position->setSelected($selectedPosition);
9067
9068
        if (is_array($arrLP)) {
9069
            reset($arrLP);
9070
        }
9071
9072
        if ($action != 'move') {
9073
            $arrHide = [];
9074
            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...
9075
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9076
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9077
                ) {
9078
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9079
                }
9080
            }
9081
9082
            if (!$no_display_add) {
9083
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9084
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9085
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
9086
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
9087
                ) {
9088
                    if (isset($_POST['content'])) {
9089
                        $content = stripslashes($_POST['content']);
9090
                    } elseif (is_array($extra_info)) {
9091
                        //If it's an html document or a text file
9092
                        if (!$no_display_edit_textarea) {
9093
                            $content = $this->display_document(
9094
                                $extra_info['path'],
9095
                                false,
9096
                                false
9097
                            );
9098
                        }
9099
                    } elseif (is_numeric($extra_info)) {
9100
                        $content = $this->display_document(
9101
                            $extra_info,
9102
                            false,
9103
                            false
9104
                        );
9105
                    } else {
9106
                        $content = '';
9107
                    }
9108
9109
                    if (!$no_display_edit_textarea) {
9110
                        // We need to calculate here some specific settings for the online editor.
9111
                        // The calculated settings work for documents in the Documents tool
9112
                        // (on the root or in subfolders).
9113
                        // For documents in native scorm packages it is unclear whether the
9114
                        // online editor should be activated or not.
9115
9116
                        // A new document, it is in the root of the repository.
9117
                        $relative_path = '';
9118
                        $relative_prefix = '';
9119
                        if (is_array($extra_info) && $extra_info != 'new') {
9120
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9121
                            $relative_path = explode('/', $extra_info['dir']);
9122
                            $cnt = count($relative_path) - 2;
9123
                            if ($cnt < 0) {
9124
                                $cnt = 0;
9125
                            }
9126
                            $relative_prefix = str_repeat('../', $cnt);
9127
                            $relative_path = array_slice($relative_path, 1, $cnt);
9128
                            $relative_path = implode('/', $relative_path);
9129
                            if (strlen($relative_path) > 0) {
9130
                                $relative_path = $relative_path.'/';
9131
                            }
9132
                        } else {
9133
                            $result = $this->generate_lp_folder($_course);
9134
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9135
                            $relative_prefix = '../../';
9136
                        }
9137
9138
                        $editor_config = [
9139
                            'ToolbarSet' => 'LearningPathDocuments',
9140
                            'Width' => '100%',
9141
                            'Height' => '500',
9142
                            'FullPage' => true,
9143
                            'CreateDocumentDir' => $relative_prefix,
9144
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9145
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9146
                        ];
9147
9148
                        if ($_GET['action'] == 'add_item') {
9149
                            $class = 'add';
9150
                            $text = get_lang('LPCreateDocument');
9151
                        } else {
9152
                            if ($_GET['action'] == 'edit_item') {
9153
                                $class = 'save';
9154
                                $text = get_lang('SaveDocument');
9155
                            }
9156
                        }
9157
9158
                        $form->addButtonSave($text, 'submit_button');
9159
                        $renderer = $form->defaultRenderer();
9160
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9161
                        $form->addElement('html', '<div class="editor-lp">');
9162
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9163
                        $form->addElement('html', '</div>');
9164
                        $defaults['content_lp'] = $content;
9165
                    }
9166
                } elseif (is_numeric($extra_info)) {
9167
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9168
9169
                    $return = $this->display_document($extra_info, true, true, true);
9170
                    $form->addElement('html', $return);
9171
                }
9172
            }
9173
        }
9174
        if (isset($extra_info['item_type']) &&
9175
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9176
        ) {
9177
            $parentSelect->freeze();
9178
            $position->freeze();
9179
        }
9180
9181
        if ($action == 'move') {
9182
            $form->addElement('hidden', 'title', $item_title);
9183
            $form->addElement('hidden', 'description', $item_description);
9184
        }
9185
        if (is_numeric($extra_info)) {
9186
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9187
            $form->addElement('hidden', 'path', $extra_info);
9188
        } elseif (is_array($extra_info)) {
9189
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9190
            $form->addElement('hidden', 'path', $extra_info['path']);
9191
        }
9192
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9193
        $form->addElement('hidden', 'post_time', time());
9194
        $form->setDefaults($defaults);
9195
9196
        return $form->returnForm();
9197
    }
9198
9199
    /**
9200
     * Returns the form to update or create a read-out text.
9201
     *
9202
     * @param string $action     "add" or "edit"
9203
     * @param int    $id         ID of the lp_item (if already exists)
9204
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9205
     *
9206
     * @throws Exception
9207
     * @throws HTML_QuickForm_Error
9208
     *
9209
     * @return string HTML form
9210
     */
9211
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9212
    {
9213
        $course_id = api_get_course_int_id();
9214
        $_course = api_get_course_info();
9215
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9216
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9217
9218
        $no_display_edit_textarea = false;
9219
        $item_description = '';
9220
        //If action==edit document
9221
        //We don't display the document form if it's not an editable document (html or txt file)
9222
        if ($action == 'edit') {
9223
            if (is_array($extra_info)) {
9224
                $path_parts = pathinfo($extra_info['dir']);
9225
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9226
                    $no_display_edit_textarea = true;
9227
                }
9228
            }
9229
        }
9230
        $no_display_add = false;
9231
9232
        if ($id != 0 && is_array($extra_info)) {
9233
            $item_title = stripslashes($extra_info['title']);
9234
            $item_description = stripslashes($extra_info['description']);
9235
            $item_terms = stripslashes($extra_info['terms']);
9236
            if (empty($item_title)) {
9237
                $path_parts = pathinfo($extra_info['path']);
9238
                $item_title = stripslashes($path_parts['filename']);
9239
            }
9240
        } elseif (is_numeric($extra_info)) {
9241
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9242
            $result = Database::query($sql);
9243
            $row = Database::fetch_array($result);
9244
            $item_title = $row['title'];
9245
            $item_title = str_replace('_', ' ', $item_title);
9246
            if (empty($item_title)) {
9247
                $path_parts = pathinfo($row['path']);
9248
                $item_title = stripslashes($path_parts['filename']);
9249
            }
9250
        } else {
9251
            $item_title = '';
9252
            $item_description = '';
9253
        }
9254
9255
        if ($id != 0 && is_array($extra_info)) {
9256
            $parent = $extra_info['parent_item_id'];
9257
        } else {
9258
            $parent = 0;
9259
        }
9260
9261
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9262
        $result = Database::query($sql);
9263
        $arrLP = [];
9264
9265
        while ($row = Database::fetch_array($result)) {
9266
            $arrLP[] = [
9267
                'id' => $row['iid'],
9268
                'item_type' => $row['item_type'],
9269
                'title' => $row['title'],
9270
                'path' => $row['path'],
9271
                'description' => $row['description'],
9272
                'parent_item_id' => $row['parent_item_id'],
9273
                'previous_item_id' => $row['previous_item_id'],
9274
                'next_item_id' => $row['next_item_id'],
9275
                'display_order' => $row['display_order'],
9276
                'max_score' => $row['max_score'],
9277
                'min_score' => $row['min_score'],
9278
                'mastery_score' => $row['mastery_score'],
9279
                'prerequisite' => $row['prerequisite'],
9280
            ];
9281
        }
9282
9283
        $this->tree_array($arrLP);
9284
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9285
        unset($this->arrMenu);
9286
9287
        if ($action == 'add') {
9288
            $formHeader = get_lang('CreateTheDocument');
9289
        } else {
9290
            $formHeader = get_lang('EditTheCurrentDocument');
9291
        }
9292
9293
        if ('edit' === $action) {
9294
            $urlAudioIcon = Display::url(
9295
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9296
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9297
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9298
            );
9299
        } else {
9300
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9301
        }
9302
9303
        $form = new FormValidator(
9304
            'frm_add_reading',
9305
            'POST',
9306
            $this->getCurrentBuildingModeURL(),
9307
            '',
9308
            ['enctype' => 'multipart/form-data']
9309
        );
9310
        $form->addHeader($formHeader);
9311
        $form->addHtml(
9312
            Display::return_message(
9313
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9314
                'normal',
9315
                false
9316
            )
9317
        );
9318
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9319
        $defaults['description'] = $item_description;
9320
9321
        $data = $this->generate_lp_folder($_course);
9322
9323
        if ($action != 'edit') {
9324
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9325
            DocumentManager::build_directory_selector(
9326
                $folders,
9327
                '',
9328
                [],
9329
                true,
9330
                $form,
9331
                'directory_parent_id'
9332
            );
9333
        }
9334
9335
        if (isset($data['id'])) {
9336
            $defaults['directory_parent_id'] = $data['id'];
9337
        }
9338
9339
        $form->addElement(
9340
            'text',
9341
            'title',
9342
            get_lang('Title')
9343
        );
9344
        $form->applyFilter('title', 'trim');
9345
        $form->applyFilter('title', 'html_filter');
9346
9347
        $arrHide[0]['value'] = $this->name;
9348
        $arrHide[0]['padding'] = 20;
9349
9350
        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...
9351
            if ($action != 'add') {
9352
                if ($arrLP[$i]['item_type'] == 'dir' &&
9353
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9354
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9355
                ) {
9356
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9357
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9358
                }
9359
            } else {
9360
                if ($arrLP[$i]['item_type'] == 'dir') {
9361
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9362
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9363
                }
9364
            }
9365
        }
9366
9367
        $parent_select = $form->addSelect(
9368
            'parent',
9369
            get_lang('Parent'),
9370
            [],
9371
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9372
        );
9373
9374
        $my_count = 0;
9375
        foreach ($arrHide as $key => $value) {
9376
            if ($my_count != 0) {
9377
                // The LP name is also the first section and is not in the same charset like the other sections.
9378
                $value['value'] = Security::remove_XSS($value['value']);
9379
                $parent_select->addOption(
9380
                    $value['value'],
9381
                    $key,
9382
                    'style="padding-left:'.$value['padding'].'px;"'
9383
                );
9384
            } else {
9385
                $value['value'] = Security::remove_XSS($value['value']);
9386
                $parent_select->addOption(
9387
                    $value['value'],
9388
                    $key,
9389
                    'style="padding-left:'.$value['padding'].'px;"'
9390
                );
9391
            }
9392
            $my_count++;
9393
        }
9394
9395
        if (!empty($id)) {
9396
            $parent_select->setSelected($parent);
9397
        } else {
9398
            $parent_item_id = Session::read('parent_item_id', 0);
9399
            $parent_select->setSelected($parent_item_id);
9400
        }
9401
9402
        if (is_array($arrLP)) {
9403
            reset($arrLP);
9404
        }
9405
9406
        $arrHide = [];
9407
        $s_selected_position = null;
9408
9409
        // POSITION
9410
        $lastPosition = null;
9411
9412
        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...
9413
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9414
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9415
            ) {
9416
                if ((isset($extra_info['previous_item_id']) &&
9417
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9418
                ) {
9419
                    $s_selected_position = $arrLP[$i]['id'];
9420
                }
9421
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9422
            }
9423
            $lastPosition = $arrLP[$i]['id'];
9424
        }
9425
9426
        if (empty($s_selected_position)) {
9427
            $s_selected_position = $lastPosition;
9428
        }
9429
9430
        $position = $form->addSelect(
9431
            'previous',
9432
            get_lang('Position'),
9433
            []
9434
        );
9435
        $position->addOption(get_lang('FirstPosition'), 0);
9436
9437
        foreach ($arrHide as $key => $value) {
9438
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9439
            $position->addOption(
9440
                $value['value'],
9441
                $key,
9442
                'style="padding-left:'.$padding.'px;"'
9443
            );
9444
        }
9445
        $position->setSelected($s_selected_position);
9446
9447
        if (is_array($arrLP)) {
9448
            reset($arrLP);
9449
        }
9450
9451
        $arrHide = [];
9452
9453
        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...
9454
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9455
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9456
            ) {
9457
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9458
            }
9459
        }
9460
9461
        if (!$no_display_add) {
9462
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9463
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9464
9465
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9466
                if (!$no_display_edit_textarea) {
9467
                    $content = '';
9468
9469
                    if (isset($_POST['content'])) {
9470
                        $content = stripslashes($_POST['content']);
9471
                    } elseif (is_array($extra_info)) {
9472
                        $content = $this->display_document($extra_info['path'], false, false);
9473
                    } elseif (is_numeric($extra_info)) {
9474
                        $content = $this->display_document($extra_info, false, false);
9475
                    }
9476
9477
                    // A new document, it is in the root of the repository.
9478
                    if (is_array($extra_info) && $extra_info != 'new') {
9479
                    } else {
9480
                        $this->generate_lp_folder($_course);
9481
                    }
9482
9483
                    if ($_GET['action'] == 'add_item') {
9484
                        $text = get_lang('LPCreateDocument');
9485
                    } else {
9486
                        $text = get_lang('SaveDocument');
9487
                    }
9488
9489
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9490
                    $form
9491
                        ->defaultRenderer()
9492
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9493
                    $form->addButtonSave($text, 'submit_button');
9494
                    $defaults['content_lp'] = $content;
9495
                }
9496
            } elseif (is_numeric($extra_info)) {
9497
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9498
9499
                $return = $this->display_document($extra_info, true, true, true);
9500
                $form->addElement('html', $return);
9501
            }
9502
        }
9503
9504
        if (is_numeric($extra_info)) {
9505
            $form->addElement('hidden', 'path', $extra_info);
9506
        } elseif (is_array($extra_info)) {
9507
            $form->addElement('hidden', 'path', $extra_info['path']);
9508
        }
9509
9510
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9511
        $form->addElement('hidden', 'post_time', time());
9512
        $form->setDefaults($defaults);
9513
9514
        return $form->returnForm();
9515
    }
9516
9517
    /**
9518
     * @param array  $courseInfo
9519
     * @param string $content
9520
     * @param string $title
9521
     * @param int    $parentId
9522
     *
9523
     * @throws \Doctrine\ORM\ORMException
9524
     * @throws \Doctrine\ORM\OptimisticLockException
9525
     * @throws \Doctrine\ORM\TransactionRequiredException
9526
     *
9527
     * @return int
9528
     */
9529
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9530
    {
9531
        $creatorId = api_get_user_id();
9532
        $sessionId = api_get_session_id();
9533
9534
        // Generates folder
9535
        $result = $this->generate_lp_folder($courseInfo);
9536
        $dir = $result['dir'];
9537
9538
        if (empty($parentId) || $parentId == '/') {
9539
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9540
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9541
9542
            if ($parentId === '/') {
9543
                $dir = '/';
9544
            }
9545
9546
            // Please, do not modify this dirname formatting.
9547
            if (strstr($dir, '..')) {
9548
                $dir = '/';
9549
            }
9550
9551
            if (!empty($dir[0]) && $dir[0] == '.') {
9552
                $dir = substr($dir, 1);
9553
            }
9554
            if (!empty($dir[0]) && $dir[0] != '/') {
9555
                $dir = '/'.$dir;
9556
            }
9557
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9558
                $dir .= '/';
9559
            }
9560
        } else {
9561
            $parentInfo = DocumentManager::get_document_data_by_id(
9562
                $parentId,
9563
                $courseInfo['code']
9564
            );
9565
            if (!empty($parentInfo)) {
9566
                $dir = $parentInfo['path'].'/';
9567
            }
9568
        }
9569
9570
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9571
9572
        if (!is_dir($filepath)) {
9573
            $dir = '/';
9574
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9575
        }
9576
9577
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9578
9579
        if (!empty($title)) {
9580
            $title = api_replace_dangerous_char(stripslashes($title));
9581
        } else {
9582
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9583
        }
9584
9585
        $title = disable_dangerous_file($title);
9586
        $filename = $title;
9587
        $content = !empty($content) ? $content : $_POST['content_lp'];
9588
        $tmpFileName = $filename;
9589
9590
        $i = 0;
9591
        while (file_exists($filepath.$tmpFileName.'.html')) {
9592
            $tmpFileName = $filename.'_'.++$i;
9593
        }
9594
9595
        $filename = $tmpFileName.'.html';
9596
        $content = stripslashes($content);
9597
9598
        if (file_exists($filepath.$filename)) {
9599
            return 0;
9600
        }
9601
9602
        $putContent = file_put_contents($filepath.$filename, $content);
9603
9604
        if ($putContent === false) {
9605
            return 0;
9606
        }
9607
9608
        $fileSize = filesize($filepath.$filename);
9609
        $saveFilePath = $dir.$filename;
9610
9611
        $document = DocumentManager::addDocument(
9612
            $courseInfo,
9613
            $saveFilePath,
9614
            'file',
9615
            $fileSize,
9616
            $tmpFileName,
9617
            '',
9618
            0, //readonly
9619
            true,
9620
            null,
9621
            $sessionId,
9622
            $creatorId
9623
        );
9624
9625
        $documentId = $document->getId();
9626
9627
        if (!$document) {
9628
            return 0;
9629
        }
9630
9631
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9632
        $newTitle = $originalTitle;
9633
9634
        if ($newComment || $newTitle) {
9635
            $em = Database::getManager();
9636
9637
            if ($newComment) {
9638
                $document->setComment($newComment);
9639
            }
9640
9641
            if ($newTitle) {
9642
                $document->setTitle($newTitle);
9643
            }
9644
9645
            $em->persist($document);
9646
            $em->flush();
9647
        }
9648
9649
        return $documentId;
9650
    }
9651
9652
    /**
9653
     * Return HTML form to add/edit a link item.
9654
     *
9655
     * @param string $action     (add/edit)
9656
     * @param int    $id         Item ID if exists
9657
     * @param mixed  $extra_info
9658
     *
9659
     * @throws Exception
9660
     * @throws HTML_QuickForm_Error
9661
     *
9662
     * @return string HTML form
9663
     */
9664
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9665
    {
9666
        $course_id = api_get_course_int_id();
9667
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9668
        $tbl_link = Database::get_course_table(TABLE_LINK);
9669
9670
        $item_title = '';
9671
        $item_description = '';
9672
        $item_url = '';
9673
9674
        if ($id != 0 && is_array($extra_info)) {
9675
            $item_title = stripslashes($extra_info['title']);
9676
            $item_description = stripslashes($extra_info['description']);
9677
            $item_url = stripslashes($extra_info['url']);
9678
        } elseif (is_numeric($extra_info)) {
9679
            $extra_info = (int) $extra_info;
9680
            $sql = "SELECT title, description, url 
9681
                    FROM $tbl_link
9682
                    WHERE c_id = $course_id AND iid = $extra_info";
9683
            $result = Database::query($sql);
9684
            $row = Database::fetch_array($result);
9685
            $item_title = $row['title'];
9686
            $item_description = $row['description'];
9687
            $item_url = $row['url'];
9688
        }
9689
9690
        $form = new FormValidator(
9691
            'edit_link',
9692
            'POST',
9693
            $this->getCurrentBuildingModeURL()
9694
        );
9695
        $defaults = [];
9696
        $parent = 0;
9697
        if ($id != 0 && is_array($extra_info)) {
9698
            $parent = $extra_info['parent_item_id'];
9699
        }
9700
9701
        $sql = "SELECT * FROM $tbl_lp_item
9702
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9703
        $result = Database::query($sql);
9704
        $arrLP = [];
9705
9706
        while ($row = Database::fetch_array($result)) {
9707
            $arrLP[] = [
9708
                'id' => $row['id'],
9709
                'item_type' => $row['item_type'],
9710
                'title' => $row['title'],
9711
                'path' => $row['path'],
9712
                'description' => $row['description'],
9713
                'parent_item_id' => $row['parent_item_id'],
9714
                'previous_item_id' => $row['previous_item_id'],
9715
                'next_item_id' => $row['next_item_id'],
9716
                'display_order' => $row['display_order'],
9717
                'max_score' => $row['max_score'],
9718
                'min_score' => $row['min_score'],
9719
                'mastery_score' => $row['mastery_score'],
9720
                'prerequisite' => $row['prerequisite'],
9721
            ];
9722
        }
9723
9724
        $this->tree_array($arrLP);
9725
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9726
        unset($this->arrMenu);
9727
9728
        if ($action == 'add') {
9729
            $legend = get_lang('CreateTheLink');
9730
        } elseif ($action == 'move') {
9731
            $legend = get_lang('MoveCurrentLink');
9732
        } else {
9733
            $legend = get_lang('EditCurrentLink');
9734
        }
9735
9736
        $form->addHeader($legend);
9737
9738
        if ($action != 'move') {
9739
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9740
            $defaults['title'] = $item_title;
9741
        }
9742
9743
        $selectParent = $form->addSelect(
9744
            'parent',
9745
            get_lang('Parent'),
9746
            [],
9747
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9748
        );
9749
        $selectParent->addOption($this->name, 0);
9750
        $arrHide = [
9751
            $id,
9752
        ];
9753
9754
        $parent_item_id = Session::read('parent_item_id', 0);
9755
9756
        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...
9757
            if ($action != 'add') {
9758
                if (
9759
                    ($arrLP[$i]['item_type'] == 'dir') &&
9760
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9761
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9762
                ) {
9763
                    $selectParent->addOption(
9764
                        $arrLP[$i]['title'],
9765
                        $arrLP[$i]['id'],
9766
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9767
                    );
9768
9769
                    if ($parent == $arrLP[$i]['id']) {
9770
                        $selectParent->setSelected($arrLP[$i]['id']);
9771
                    }
9772
                } else {
9773
                    $arrHide[] = $arrLP[$i]['id'];
9774
                }
9775
            } else {
9776
                if ($arrLP[$i]['item_type'] == 'dir') {
9777
                    $selectParent->addOption(
9778
                        $arrLP[$i]['title'],
9779
                        $arrLP[$i]['id'],
9780
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9781
                    );
9782
9783
                    if ($parent_item_id == $arrLP[$i]['id']) {
9784
                        $selectParent->setSelected($arrLP[$i]['id']);
9785
                    }
9786
                }
9787
            }
9788
        }
9789
9790
        if (is_array($arrLP)) {
9791
            reset($arrLP);
9792
        }
9793
9794
        $selectPrevious = $form->addSelect(
9795
            'previous',
9796
            get_lang('Position'),
9797
            [],
9798
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9799
        );
9800
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9801
9802
        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...
9803
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9804
                $selectPrevious->addOption(
9805
                    $arrLP[$i]['title'],
9806
                    $arrLP[$i]['id']
9807
                );
9808
9809
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9810
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9811
                } elseif ($action == 'add') {
9812
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9813
                }
9814
            }
9815
        }
9816
9817
        if ($action != 'move') {
9818
            $urlAttributes = ['class' => 'learnpath_item_form'];
9819
9820
            if (is_numeric($extra_info)) {
9821
                $urlAttributes['disabled'] = 'disabled';
9822
            }
9823
9824
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9825
            $defaults['url'] = $item_url;
9826
            $arrHide = [];
9827
            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...
9828
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9829
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9830
                }
9831
            }
9832
        }
9833
9834
        if ($action == 'add') {
9835
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9836
        } else {
9837
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9838
        }
9839
9840
        if ($action == 'move') {
9841
            $form->addHidden('title', $item_title);
9842
            $form->addHidden('description', $item_description);
9843
        }
9844
9845
        if (is_numeric($extra_info)) {
9846
            $form->addHidden('path', $extra_info);
9847
        } elseif (is_array($extra_info)) {
9848
            $form->addHidden('path', $extra_info['path']);
9849
        }
9850
        $form->addHidden('type', TOOL_LINK);
9851
        $form->addHidden('post_time', time());
9852
        $form->setDefaults($defaults);
9853
9854
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9855
    }
9856
9857
    /**
9858
     * Return HTML form to add/edit a student publication (work).
9859
     *
9860
     * @param string $action
9861
     * @param int    $id         Item ID if already exists
9862
     * @param string $extra_info
9863
     *
9864
     * @throws Exception
9865
     *
9866
     * @return string HTML form
9867
     */
9868
    public function display_student_publication_form(
9869
        $action = 'add',
9870
        $id = 0,
9871
        $extra_info = ''
9872
    ) {
9873
        $course_id = api_get_course_int_id();
9874
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9875
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9876
9877
        $item_title = get_lang('Student_publication');
9878
        if ($id != 0 && is_array($extra_info)) {
9879
            $item_title = stripslashes($extra_info['title']);
9880
            $item_description = stripslashes($extra_info['description']);
9881
        } elseif (is_numeric($extra_info)) {
9882
            $extra_info = (int) $extra_info;
9883
            $sql = "SELECT title, description
9884
                    FROM $tbl_publication
9885
                    WHERE c_id = $course_id AND id = ".$extra_info;
9886
9887
            $result = Database::query($sql);
9888
            $row = Database::fetch_array($result);
9889
            if ($row) {
9890
                $item_title = $row['title'];
9891
            }
9892
        }
9893
9894
        $parent = 0;
9895
        if ($id != 0 && is_array($extra_info)) {
9896
            $parent = $extra_info['parent_item_id'];
9897
        }
9898
9899
        $sql = "SELECT * FROM $tbl_lp_item 
9900
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9901
        $result = Database::query($sql);
9902
        $arrLP = [];
9903
9904
        while ($row = Database::fetch_array($result)) {
9905
            $arrLP[] = [
9906
                'id' => $row['iid'],
9907
                'item_type' => $row['item_type'],
9908
                'title' => $row['title'],
9909
                'path' => $row['path'],
9910
                'description' => $row['description'],
9911
                'parent_item_id' => $row['parent_item_id'],
9912
                'previous_item_id' => $row['previous_item_id'],
9913
                'next_item_id' => $row['next_item_id'],
9914
                'display_order' => $row['display_order'],
9915
                'max_score' => $row['max_score'],
9916
                'min_score' => $row['min_score'],
9917
                'mastery_score' => $row['mastery_score'],
9918
                'prerequisite' => $row['prerequisite'],
9919
            ];
9920
        }
9921
9922
        $this->tree_array($arrLP);
9923
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9924
        unset($this->arrMenu);
9925
9926
        $form = new FormValidator('frm_student_publication', 'post', '#');
9927
9928
        if ($action == 'add') {
9929
            $form->addHeader(get_lang('Student_publication'));
9930
        } elseif ($action == 'move') {
9931
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9932
        } else {
9933
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9934
        }
9935
9936
        if ($action != 'move') {
9937
            $form->addText(
9938
                'title',
9939
                get_lang('Title'),
9940
                true,
9941
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9942
            );
9943
        }
9944
9945
        $parentSelect = $form->addSelect(
9946
            'parent',
9947
            get_lang('Parent'),
9948
            ['0' => $this->name],
9949
            [
9950
                'onchange' => 'javascript: load_cbo(this.value);',
9951
                'class' => 'learnpath_item_form',
9952
                'id' => 'idParent',
9953
            ]
9954
        );
9955
9956
        $arrHide = [$id];
9957
        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...
9958
            if ($action != 'add') {
9959
                if (
9960
                    ($arrLP[$i]['item_type'] == 'dir') &&
9961
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9962
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9963
                ) {
9964
                    $parentSelect->addOption(
9965
                        $arrLP[$i]['title'],
9966
                        $arrLP[$i]['id'],
9967
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9968
                    );
9969
9970
                    if ($parent == $arrLP[$i]['id']) {
9971
                        $parentSelect->setSelected($arrLP[$i]['id']);
9972
                    }
9973
                } else {
9974
                    $arrHide[] = $arrLP[$i]['id'];
9975
                }
9976
            } else {
9977
                if ($arrLP[$i]['item_type'] == 'dir') {
9978
                    $parentSelect->addOption(
9979
                        $arrLP[$i]['title'],
9980
                        $arrLP[$i]['id'],
9981
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9982
                    );
9983
9984
                    if ($parent == $arrLP[$i]['id']) {
9985
                        $parentSelect->setSelected($arrLP[$i]['id']);
9986
                    }
9987
                }
9988
            }
9989
        }
9990
9991
        if (is_array($arrLP)) {
9992
            reset($arrLP);
9993
        }
9994
9995
        $previousSelect = $form->addSelect(
9996
            'previous',
9997
            get_lang('Position'),
9998
            ['0' => get_lang('FirstPosition')],
9999
            ['id' => 'previous', 'class' => 'learnpath_item_form']
10000
        );
10001
10002
        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...
10003
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
10004
                $previousSelect->addOption(
10005
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
10006
                    $arrLP[$i]['id']
10007
                );
10008
10009
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
10010
                    $previousSelect->setSelected($arrLP[$i]['id']);
10011
                } elseif ($action == 'add') {
10012
                    $previousSelect->setSelected($arrLP[$i]['id']);
10013
                }
10014
            }
10015
        }
10016
10017
        if ($action == 'add') {
10018
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
10019
        } else {
10020
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
10021
        }
10022
10023
        if ($action == 'move') {
10024
            $form->addHidden('title', $item_title);
10025
            $form->addHidden('description', $item_description);
10026
        }
10027
10028
        if (is_numeric($extra_info)) {
10029
            $form->addHidden('path', $extra_info);
10030
        } elseif (is_array($extra_info)) {
10031
            $form->addHidden('path', $extra_info['path']);
10032
        }
10033
10034
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
10035
        $form->addHidden('post_time', time());
10036
        $form->setDefaults(['title' => $item_title]);
10037
10038
        $return = '<div class="sectioncomment">';
10039
        $return .= $form->returnForm();
10040
        $return .= '</div>';
10041
10042
        return $return;
10043
    }
10044
10045
    /**
10046
     * Displays the menu for manipulating a step.
10047
     *
10048
     * @param id     $item_id
10049
     * @param string $item_type
10050
     *
10051
     * @return string
10052
     */
10053
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10054
    {
10055
        $_course = api_get_course_info();
10056
        $course_code = api_get_course_id();
10057
        $return = '<div class="actions">';
10058
10059
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10060
        $item_id = (int) $item_id;
10061
        $sql = "SELECT * FROM $tbl_lp_item 
10062
                WHERE iid = ".$item_id;
10063
        $result = Database::query($sql);
10064
        $row = Database::fetch_assoc($result);
10065
10066
        $audio_player = null;
10067
        // We display an audio player if needed.
10068
        if (!empty($row['audio'])) {
10069
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
10070
10071
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
10072
                .'<audio src="'.$webAudioPath.'" controls>'
10073
                .'</div><br>';
10074
        }
10075
10076
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10077
10078
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10079
            $return .= Display::url(
10080
                Display::return_icon(
10081
                    'edit.png',
10082
                    get_lang('Edit'),
10083
                    [],
10084
                    ICON_SIZE_SMALL
10085
                ),
10086
                $url.'&action=edit_item&path_item='.$row['path']
10087
            );
10088
10089
            $return .= Display::url(
10090
                Display::return_icon(
10091
                    'move.png',
10092
                    get_lang('Move'),
10093
                    [],
10094
                    ICON_SIZE_SMALL
10095
                ),
10096
                $url.'&action=move_item'
10097
            );
10098
        }
10099
10100
        // Commented for now as prerequisites cannot be added to chapters.
10101
        if ($item_type != 'dir') {
10102
            $return .= Display::url(
10103
                Display::return_icon(
10104
                    'accept.png',
10105
                    get_lang('LearnpathPrerequisites'),
10106
                    [],
10107
                    ICON_SIZE_SMALL
10108
                ),
10109
                $url.'&action=edit_item_prereq'
10110
            );
10111
        }
10112
        $return .= Display::url(
10113
            Display::return_icon(
10114
                'delete.png',
10115
                get_lang('Delete'),
10116
                [],
10117
                ICON_SIZE_SMALL
10118
            ),
10119
            $url.'&action=delete_item'
10120
        );
10121
10122
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10123
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10124
            if (empty($documentData)) {
10125
                // Try with iid
10126
                $table = Database::get_course_table(TABLE_DOCUMENT);
10127
                $sql = "SELECT path FROM $table
10128
                        WHERE 
10129
                              c_id = ".api_get_course_int_id()." AND 
10130
                              iid = ".$row['path']." AND 
10131
                              path NOT LIKE '%_DELETED_%'";
10132
                $result = Database::query($sql);
10133
                $documentData = Database::fetch_array($result);
10134
                if ($documentData) {
10135
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10136
                }
10137
            }
10138
            if (isset($documentData['absolute_path_from_document'])) {
10139
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10140
            }
10141
        }
10142
10143
        $return .= '</div>';
10144
10145
        if (!empty($audio_player)) {
10146
            $return .= $audio_player;
10147
        }
10148
10149
        return $return;
10150
    }
10151
10152
    /**
10153
     * Creates the javascript needed for filling up the checkboxes without page reload.
10154
     *
10155
     * @return string
10156
     */
10157
    public function get_js_dropdown_array()
10158
    {
10159
        $course_id = api_get_course_int_id();
10160
        $return = 'var child_name = new Array();'."\n";
10161
        $return .= 'var child_value = new Array();'."\n\n";
10162
        $return .= 'child_name[0] = new Array();'."\n";
10163
        $return .= 'child_value[0] = new Array();'."\n\n";
10164
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10165
        $sql = "SELECT * FROM ".$tbl_lp_item."
10166
                WHERE 
10167
                    c_id = $course_id AND 
10168
                    lp_id = ".$this->lp_id." AND 
10169
                    parent_item_id = 0
10170
                ORDER BY display_order ASC";
10171
        $res_zero = Database::query($sql);
10172
        $i = 0;
10173
10174
        while ($row_zero = Database::fetch_array($res_zero)) {
10175
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10176
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10177
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10178
                }
10179
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10180
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10181
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10182
            }
10183
        }
10184
        $return .= "\n";
10185
        $sql = "SELECT * FROM $tbl_lp_item
10186
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10187
        $res = Database::query($sql);
10188
        while ($row = Database::fetch_array($res)) {
10189
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10190
                           WHERE
10191
                                c_id = ".$course_id." AND
10192
                                parent_item_id = ".$row['iid']."
10193
                           ORDER BY display_order ASC";
10194
            $res_parent = Database::query($sql_parent);
10195
            $i = 0;
10196
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10197
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10198
10199
            while ($row_parent = Database::fetch_array($res_parent)) {
10200
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
10201
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10202
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10203
            }
10204
            $return .= "\n";
10205
        }
10206
10207
        $return .= "
10208
            function load_cbo(id) {
10209
                if (!id) {
10210
                    return false;
10211
                }
10212
            
10213
                var cbo = document.getElementById('previous');
10214
                for(var i = cbo.length - 1; i > 0; i--) {
10215
                    cbo.options[i] = null;
10216
                }
10217
            
10218
                var k=0;
10219
                for(var i = 1; i <= child_name[id].length; i++){
10220
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10221
                    option.style.paddingLeft = '40px';
10222
                    cbo.options[i] = option;
10223
                    k = i;
10224
                }
10225
            
10226
                cbo.options[k].selected = true;
10227
                $('#previous').selectpicker('refresh');
10228
            }";
10229
10230
        return $return;
10231
    }
10232
10233
    /**
10234
     * Display the form to allow moving an item.
10235
     *
10236
     * @param int $item_id Item ID
10237
     *
10238
     * @throws Exception
10239
     * @throws HTML_QuickForm_Error
10240
     *
10241
     * @return string HTML form
10242
     */
10243
    public function display_move_item($item_id)
10244
    {
10245
        $return = '';
10246
        if (is_numeric($item_id)) {
10247
            $item_id = (int) $item_id;
10248
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10249
10250
            $sql = "SELECT * FROM $tbl_lp_item
10251
                    WHERE iid = $item_id";
10252
            $res = Database::query($sql);
10253
            $row = Database::fetch_array($res);
10254
10255
            switch ($row['item_type']) {
10256
                case 'dir':
10257
                case 'asset':
10258
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10259
                    $return .= $this->display_item_form(
10260
                        $row['item_type'],
10261
                        get_lang('MoveCurrentChapter'),
10262
                        'move',
10263
                        $item_id,
10264
                        $row
10265
                    );
10266
                    break;
10267
                case TOOL_DOCUMENT:
10268
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10269
                    $return .= $this->display_document_form('move', $item_id, $row);
10270
                    break;
10271
                case TOOL_LINK:
10272
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10273
                    $return .= $this->display_link_form('move', $item_id, $row);
10274
                    break;
10275
                case TOOL_HOTPOTATOES:
10276
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10277
                    $return .= $this->display_link_form('move', $item_id, $row);
10278
                    break;
10279
                case TOOL_QUIZ:
10280
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10281
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10282
                    break;
10283
                case TOOL_STUDENTPUBLICATION:
10284
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10285
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10286
                    break;
10287
                case TOOL_FORUM:
10288
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10289
                    $return .= $this->display_forum_form('move', $item_id, $row);
10290
                    break;
10291
                case TOOL_THREAD:
10292
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10293
                    $return .= $this->display_forum_form('move', $item_id, $row);
10294
                    break;
10295
            }
10296
        }
10297
10298
        return $return;
10299
    }
10300
10301
    /**
10302
     * Return HTML form to allow prerequisites selection.
10303
     *
10304
     * @todo use FormValidator
10305
     *
10306
     * @param int Item ID
10307
     *
10308
     * @return string HTML form
10309
     */
10310
    public function display_item_prerequisites_form($item_id = 0)
10311
    {
10312
        $course_id = api_get_course_int_id();
10313
        $item_id = (int) $item_id;
10314
10315
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10316
10317
        /* Current prerequisite */
10318
        $sql = "SELECT * FROM $tbl_lp_item
10319
                WHERE iid = $item_id";
10320
        $result = Database::query($sql);
10321
        $row = Database::fetch_array($result);
10322
        $prerequisiteId = $row['prerequisite'];
10323
        $return = '<legend>';
10324
        $return .= get_lang('AddEditPrerequisites');
10325
        $return .= '</legend>';
10326
        $return .= '<form method="POST">';
10327
        $return .= '<div class="table-responsive">';
10328
        $return .= '<table class="table table-hover">';
10329
        $return .= '<thead>';
10330
        $return .= '<tr>';
10331
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10332
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10333
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10334
        $return .= '</tr>';
10335
        $return .= '</thead>';
10336
10337
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10338
        $return .= '<tbody>';
10339
        $return .= '<tr>';
10340
        $return .= '<td colspan="3">';
10341
        $return .= '<div class="radio learnpath"><label for="idNone">';
10342
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10343
        $return .= get_lang('None').'</label>';
10344
        $return .= '</div>';
10345
        $return .= '</tr>';
10346
10347
        $sql = "SELECT * FROM $tbl_lp_item
10348
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10349
        $result = Database::query($sql);
10350
        $arrLP = [];
10351
10352
        $selectedMinScore = [];
10353
        $selectedMaxScore = [];
10354
        $masteryScore = [];
10355
        while ($row = Database::fetch_array($result)) {
10356
            if ($row['iid'] == $item_id) {
10357
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10358
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10359
            }
10360
            $masteryScore[$row['iid']] = $row['mastery_score'];
10361
10362
            $arrLP[] = [
10363
                'id' => $row['iid'],
10364
                'item_type' => $row['item_type'],
10365
                'title' => $row['title'],
10366
                'ref' => $row['ref'],
10367
                'description' => $row['description'],
10368
                'parent_item_id' => $row['parent_item_id'],
10369
                'previous_item_id' => $row['previous_item_id'],
10370
                'next_item_id' => $row['next_item_id'],
10371
                'max_score' => $row['max_score'],
10372
                'min_score' => $row['min_score'],
10373
                'mastery_score' => $row['mastery_score'],
10374
                'prerequisite' => $row['prerequisite'],
10375
                'display_order' => $row['display_order'],
10376
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10377
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10378
            ];
10379
        }
10380
10381
        $this->tree_array($arrLP);
10382
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10383
        unset($this->arrMenu);
10384
10385
        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...
10386
            $item = $arrLP[$i];
10387
10388
            if ($item['id'] == $item_id) {
10389
                break;
10390
            }
10391
10392
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10393
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10394
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10395
10396
            $return .= '<tr>';
10397
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10398
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10399
            $return .= '<label for="id'.$item['id'].'">';
10400
            $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'].'" />';
10401
10402
            $icon_name = str_replace(' ', '', $item['item_type']);
10403
10404
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10405
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10406
            } else {
10407
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10408
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10409
                } else {
10410
                    $return .= Display::return_icon('folder_document.png');
10411
                }
10412
            }
10413
10414
            $return .= $item['title'].'</label>';
10415
            $return .= '</div>';
10416
            $return .= '</td>';
10417
10418
            if ($item['item_type'] == TOOL_QUIZ) {
10419
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10420
                $lpItemObj = new LpItem($course_id, $item['id']);
10421
                $exercise = new Exercise($course_id);
10422
                $exercise->read($lpItemObj->path);
10423
                $lpItemObj->max_score = $exercise->get_max_score();
10424
                $lpItemObj->update();
10425
                $item['max_score'] = $lpItemObj->max_score;
10426
10427
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10428
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10429
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10430
                }
10431
10432
                $return .= '<td>';
10433
                $return .= '<input 
10434
                    class="form-control" 
10435
                    size="4" maxlength="3" 
10436
                    name="min_'.$item['id'].'" 
10437
                    type="number" 
10438
                    min="0" 
10439
                    step="1" 
10440
                    max="'.$item['max_score'].'" 
10441
                    value="'.$selectedMinScoreValue.'" 
10442
                />';
10443
                $return .= '</td>';
10444
                $return .= '<td>';
10445
                $return .= '<input 
10446
                    class="form-control" 
10447
                    size="4" 
10448
                    maxlength="3" 
10449
                    name="max_'.$item['id'].'" 
10450
                    type="number" 
10451
                    min="0" 
10452
                    step="1" 
10453
                    max="'.$item['max_score'].'" 
10454
                    value="'.$selectedMaxScoreValue.'" 
10455
                />';
10456
                $return .= '</td>';
10457
            }
10458
10459
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10460
                $return .= '<td>';
10461
                $return .= '<input 
10462
                    size="4" 
10463
                    maxlength="3" 
10464
                    name="min_'.$item['id'].'" 
10465
                    type="number" 
10466
                    min="0" 
10467
                    step="1" 
10468
                    max="'.$item['max_score'].'" 
10469
                    value="'.$selectedMinScoreValue.'" 
10470
                />';
10471
                $return .= '</td>';
10472
                $return .= '<td>';
10473
                $return .= '<input 
10474
                    size="4" 
10475
                    maxlength="3" 
10476
                    name="max_'.$item['id'].'" 
10477
                    type="number" 
10478
                    min="0" 
10479
                    step="1" 
10480
                    max="'.$item['max_score'].'" 
10481
                    value="'.$selectedMaxScoreValue.'" 
10482
                />';
10483
                $return .= '</td>';
10484
            }
10485
            $return .= '</tr>';
10486
        }
10487
        $return .= '<tr>';
10488
        $return .= '</tr>';
10489
        $return .= '</tbody>';
10490
        $return .= '</table>';
10491
        $return .= '</div>';
10492
        $return .= '<div class="form-group">';
10493
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10494
            get_lang('ModifyPrerequisites').'</button>';
10495
        $return .= '</form>';
10496
10497
        return $return;
10498
    }
10499
10500
    /**
10501
     * Return HTML list to allow prerequisites selection for lp.
10502
     *
10503
     * @return string HTML form
10504
     */
10505
    public function display_lp_prerequisites_list()
10506
    {
10507
        $course_id = api_get_course_int_id();
10508
        $lp_id = $this->lp_id;
10509
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10510
10511
        // get current prerequisite
10512
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10513
        $result = Database::query($sql);
10514
        $row = Database::fetch_array($result);
10515
        $prerequisiteId = $row['prerequisite'];
10516
        $session_id = api_get_session_id();
10517
        $session_condition = api_get_session_condition($session_id, true, true);
10518
        $sql = "SELECT * FROM $tbl_lp
10519
                WHERE c_id = $course_id $session_condition
10520
                ORDER BY display_order ";
10521
        $rs = Database::query($sql);
10522
        $return = '';
10523
        $return .= '<select name="prerequisites" class="form-control">';
10524
        $return .= '<option value="0">'.get_lang('None').'</option>';
10525
        if (Database::num_rows($rs) > 0) {
10526
            while ($row = Database::fetch_array($rs)) {
10527
                if ($row['id'] == $lp_id) {
10528
                    continue;
10529
                }
10530
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10531
            }
10532
        }
10533
        $return .= '</select>';
10534
10535
        return $return;
10536
    }
10537
10538
    /**
10539
     * Creates a list with all the documents in it.
10540
     *
10541
     * @param bool $showInvisibleFiles
10542
     *
10543
     * @throws Exception
10544
     * @throws HTML_QuickForm_Error
10545
     *
10546
     * @return string
10547
     */
10548
    public function get_documents($showInvisibleFiles = false)
10549
    {
10550
        $course_info = api_get_course_info();
10551
        $sessionId = api_get_session_id();
10552
        $documentTree = DocumentManager::get_document_preview(
10553
            $course_info,
10554
            $this->lp_id,
10555
            null,
10556
            $sessionId,
10557
            true,
10558
            null,
10559
            null,
10560
            $showInvisibleFiles,
10561
            true
10562
        );
10563
10564
        $headers = [
10565
            get_lang('Files'),
10566
            get_lang('CreateTheDocument'),
10567
            get_lang('CreateReadOutText'),
10568
            get_lang('Upload'),
10569
        ];
10570
10571
        $form = new FormValidator(
10572
            'form_upload',
10573
            'POST',
10574
            $this->getCurrentBuildingModeURL(),
10575
            '',
10576
            ['enctype' => 'multipart/form-data']
10577
        );
10578
10579
        $folders = DocumentManager::get_all_document_folders(
10580
            api_get_course_info(),
10581
            0,
10582
            true
10583
        );
10584
10585
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10586
10587
        DocumentManager::build_directory_selector(
10588
            $folders,
10589
            $lpPathInfo['id'],
10590
            [],
10591
            true,
10592
            $form,
10593
            'directory_parent_id'
10594
        );
10595
10596
        $group = [
10597
            $form->createElement(
10598
                'radio',
10599
                'if_exists',
10600
                get_lang('UplWhatIfFileExists'),
10601
                get_lang('UplDoNothing'),
10602
                'nothing'
10603
            ),
10604
            $form->createElement(
10605
                'radio',
10606
                'if_exists',
10607
                null,
10608
                get_lang('UplOverwriteLong'),
10609
                'overwrite'
10610
            ),
10611
            $form->createElement(
10612
                'radio',
10613
                'if_exists',
10614
                null,
10615
                get_lang('UplRenameLong'),
10616
                'rename'
10617
            ),
10618
        ];
10619
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10620
10621
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10622
        $defaultFileExistsOption = 'rename';
10623
        if (!empty($fileExistsOption)) {
10624
            $defaultFileExistsOption = $fileExistsOption;
10625
        }
10626
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10627
10628
        // Check box options
10629
        $form->addElement(
10630
            'checkbox',
10631
            'unzip',
10632
            get_lang('Options'),
10633
            get_lang('Uncompress')
10634
        );
10635
10636
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10637
        $form->addMultipleUpload($url);
10638
        $new = $this->display_document_form('add', 0);
10639
        $frmReadOutText = $this->displayFrmReadOutText('add');
10640
        $tabs = Display::tabs(
10641
            $headers,
10642
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10643
            'subtab'
10644
        );
10645
10646
        return $tabs;
10647
    }
10648
10649
    /**
10650
     * Creates a list with all the exercises (quiz) in it.
10651
     *
10652
     * @return string
10653
     */
10654
    public function get_exercises()
10655
    {
10656
        $course_id = api_get_course_int_id();
10657
        $session_id = api_get_session_id();
10658
        $userInfo = api_get_user_info();
10659
10660
        // New for hotpotatoes.
10661
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10662
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10663
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10664
        $condition_session = api_get_session_condition($session_id, true, true);
10665
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10666
10667
        $activeCondition = ' active <> -1 ';
10668
        if ($setting) {
10669
            $activeCondition = ' active = 1 ';
10670
        }
10671
10672
        $sql_quiz = "SELECT * FROM $tbl_quiz
10673
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10674
                     ORDER BY title ASC";
10675
10676
        $sql_hot = "SELECT * FROM $tbl_doc
10677
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10678
                     ORDER BY id ASC";
10679
10680
        $res_quiz = Database::query($sql_quiz);
10681
        $res_hot = Database::query($sql_hot);
10682
10683
        $return = '<ul class="lp_resource">';
10684
        $return .= '<li class="lp_resource_element">';
10685
        $return .= Display::return_icon('new_exercice.png');
10686
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10687
            get_lang('NewExercise').'</a>';
10688
        $return .= '</li>';
10689
10690
        $previewIcon = Display::return_icon(
10691
            'preview_view.png',
10692
            get_lang('Preview')
10693
        );
10694
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10695
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10696
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10697
10698
        // Display hotpotatoes
10699
        while ($row_hot = Database::fetch_array($res_hot)) {
10700
            $link = Display::url(
10701
                $previewIcon,
10702
                $exerciseUrl.'&file='.$row_hot['path'],
10703
                ['target' => '_blank']
10704
            );
10705
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10706
            $return .= '<a class="moved" href="#">';
10707
            $return .= Display::return_icon(
10708
                'move_everywhere.png',
10709
                get_lang('Move'),
10710
                [],
10711
                ICON_SIZE_TINY
10712
            );
10713
            $return .= '</a> ';
10714
            $return .= Display::return_icon('hotpotatoes_s.png');
10715
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10716
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10717
            $return .= '</li>';
10718
        }
10719
10720
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10721
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10722
            $title = strip_tags(
10723
                api_html_entity_decode($row_quiz['title'])
10724
            );
10725
10726
            $visibility = api_get_item_visibility(
10727
                ['real_id' => $course_id],
10728
                TOOL_QUIZ,
10729
                $row_quiz['iid'],
10730
                $session_id
10731
            );
10732
10733
            $link = Display::url(
10734
                $previewIcon,
10735
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10736
                ['target' => '_blank']
10737
            );
10738
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10739
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10740
            $return .= $quizIcon;
10741
            $sessionStar = api_get_session_image(
10742
                $row_quiz['session_id'],
10743
                $userInfo['status']
10744
            );
10745
            $return .= Display::url(
10746
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10747
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10748
                [
10749
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10750
                ]
10751
            );
10752
10753
            $return .= '</li>';
10754
        }
10755
10756
        $return .= '</ul>';
10757
10758
        return $return;
10759
    }
10760
10761
    /**
10762
     * Creates a list with all the links in it.
10763
     *
10764
     * @return string
10765
     */
10766
    public function get_links()
10767
    {
10768
        $selfUrl = api_get_self();
10769
        $courseIdReq = api_get_cidreq();
10770
        $course = api_get_course_info();
10771
        $userInfo = api_get_user_info();
10772
10773
        $course_id = $course['real_id'];
10774
        $tbl_link = Database::get_course_table(TABLE_LINK);
10775
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10776
        $moveEverywhereIcon = Display::return_icon(
10777
            'move_everywhere.png',
10778
            get_lang('Move'),
10779
            [],
10780
            ICON_SIZE_TINY
10781
        );
10782
10783
        $session_id = api_get_session_id();
10784
        $condition_session = api_get_session_condition(
10785
            $session_id,
10786
            true,
10787
            true,
10788
            'link.session_id'
10789
        );
10790
10791
        $sql = "SELECT 
10792
                    link.id as link_id,
10793
                    link.title as link_title,
10794
                    link.session_id as link_session_id,
10795
                    link.category_id as category_id,
10796
                    link_category.category_title as category_title
10797
                FROM $tbl_link as link
10798
                LEFT JOIN $linkCategoryTable as link_category
10799
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10800
                WHERE link.c_id = $course_id $condition_session
10801
                ORDER BY link_category.category_title ASC, link.title ASC";
10802
        $result = Database::query($sql);
10803
        $categorizedLinks = [];
10804
        $categories = [];
10805
10806
        while ($link = Database::fetch_array($result)) {
10807
            if (!$link['category_id']) {
10808
                $link['category_title'] = get_lang('Uncategorized');
10809
            }
10810
            $categories[$link['category_id']] = $link['category_title'];
10811
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10812
        }
10813
10814
        $linksHtmlCode =
10815
            '<script>
10816
            function toggle_tool(tool, id) {
10817
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10818
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10819
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10820
                } else {
10821
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10822
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10823
                }
10824
            }
10825
        </script>
10826
10827
        <ul class="lp_resource">
10828
            <li class="lp_resource_element">
10829
                '.Display::return_icon('linksnew.gif').'
10830
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10831
                get_lang('LinkAdd').'
10832
                </a>
10833
            </li>';
10834
10835
        foreach ($categorizedLinks as $categoryId => $links) {
10836
            $linkNodes = null;
10837
            foreach ($links as $key => $linkInfo) {
10838
                $title = $linkInfo['link_title'];
10839
                $linkSessionId = $linkInfo['link_session_id'];
10840
10841
                $link = Display::url(
10842
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10843
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10844
                    ['target' => '_blank']
10845
                );
10846
10847
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10848
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10849
                    $linkNodes .=
10850
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10851
                        <a class="moved" href="#">'.
10852
                            $moveEverywhereIcon.
10853
                        '</a>
10854
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10855
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10856
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10857
                        Security::remove_XSS($title).$sessionStar.$link.
10858
                        '</a>
10859
                    </li>';
10860
                }
10861
            }
10862
            $linksHtmlCode .=
10863
                '<li>
10864
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10865
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10866
                    align="absbottom" />
10867
                </a>
10868
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10869
            </li>
10870
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10871
        }
10872
        $linksHtmlCode .= '</ul>';
10873
10874
        return $linksHtmlCode;
10875
    }
10876
10877
    /**
10878
     * Creates a list with all the student publications in it.
10879
     *
10880
     * @return string
10881
     */
10882
    public function get_student_publications()
10883
    {
10884
        $return = '<ul class="lp_resource">';
10885
        $return .= '<li class="lp_resource_element">';
10886
        $return .= Display::return_icon('works_new.gif');
10887
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10888
            get_lang('AddAssignmentPage').'</a>';
10889
        $return .= '</li>';
10890
10891
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10892
        $works = getWorkListTeacher(0, 100, null, null, null);
10893
        if (!empty($works)) {
10894
            foreach ($works as $work) {
10895
                $link = Display::url(
10896
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10897
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10898
                    ['target' => '_blank']
10899
                );
10900
10901
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10902
                $return .= '<a class="moved" href="#">';
10903
                $return .= Display::return_icon(
10904
                    'move_everywhere.png',
10905
                    get_lang('Move'),
10906
                    [],
10907
                    ICON_SIZE_TINY
10908
                );
10909
                $return .= '</a> ';
10910
10911
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10912
                $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.'">'.
10913
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10914
                </a>';
10915
10916
                $return .= '</li>';
10917
            }
10918
        }
10919
10920
        $return .= '</ul>';
10921
10922
        return $return;
10923
    }
10924
10925
    /**
10926
     * Creates a list with all the forums in it.
10927
     *
10928
     * @return string
10929
     */
10930
    public function get_forums()
10931
    {
10932
        require_once '../forum/forumfunction.inc.php';
10933
10934
        $forumCategories = get_forum_categories();
10935
        $forumsInNoCategory = get_forums_in_category(0);
10936
        if (!empty($forumsInNoCategory)) {
10937
            $forumCategories = array_merge(
10938
                $forumCategories,
10939
                [
10940
                    [
10941
                        'cat_id' => 0,
10942
                        'session_id' => 0,
10943
                        'visibility' => 1,
10944
                        'cat_comment' => null,
10945
                    ],
10946
                ]
10947
            );
10948
        }
10949
10950
        $forumList = get_forums();
10951
        $a_forums = [];
10952
        foreach ($forumCategories as $forumCategory) {
10953
            // The forums in this category.
10954
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10955
            if (!empty($forumsInCategory)) {
10956
                foreach ($forumList as $forum) {
10957
                    if (isset($forum['forum_category']) &&
10958
                        $forum['forum_category'] == $forumCategory['cat_id']
10959
                    ) {
10960
                        $a_forums[] = $forum;
10961
                    }
10962
                }
10963
            }
10964
        }
10965
10966
        $return = '<ul class="lp_resource">';
10967
10968
        // First add link
10969
        $return .= '<li class="lp_resource_element">';
10970
        $return .= Display::return_icon('new_forum.png');
10971
        $return .= Display::url(
10972
            get_lang('CreateANewForum'),
10973
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10974
                'action' => 'add',
10975
                'content' => 'forum',
10976
                'lp_id' => $this->lp_id,
10977
            ]),
10978
            ['title' => get_lang('CreateANewForum')]
10979
        );
10980
        $return .= '</li>';
10981
10982
        $return .= '<script>
10983
            function toggle_forum(forum_id) {
10984
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10985
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10986
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10987
                } else {
10988
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10989
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10990
                }
10991
            }
10992
        </script>';
10993
10994
        foreach ($a_forums as $forum) {
10995
            if (!empty($forum['forum_id'])) {
10996
                $link = Display::url(
10997
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10998
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10999
                    ['target' => '_blank']
11000
                );
11001
11002
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
11003
                $return .= '<a class="moved" href="#">';
11004
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11005
                $return .= ' </a>';
11006
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
11007
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
11008
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
11009
                            </a>
11010
                            <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">'.
11011
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
11012
11013
                $return .= '</li>';
11014
11015
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
11016
                $a_threads = get_threads($forum['forum_id']);
11017
                if (is_array($a_threads)) {
11018
                    foreach ($a_threads as $thread) {
11019
                        $link = Display::url(
11020
                            Display::return_icon('preview_view.png', get_lang('Preview')),
11021
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
11022
                            ['target' => '_blank']
11023
                        );
11024
11025
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
11026
                        $return .= '&nbsp;<a class="moved" href="#">';
11027
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11028
                        $return .= ' </a>';
11029
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
11030
                        $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.'">'.
11031
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
11032
                        $return .= '</li>';
11033
                    }
11034
                }
11035
                $return .= '</div>';
11036
            }
11037
        }
11038
        $return .= '</ul>';
11039
11040
        return $return;
11041
    }
11042
11043
    /**
11044
     * // TODO: The output encoding should be equal to the system encoding.
11045
     *
11046
     * Exports the learning path as a SCORM package. This is the main function that
11047
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11048
     * whole thing and returns the zip.
11049
     *
11050
     * This method needs to be called in PHP5, as it will fail with non-adequate
11051
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11052
     * you need to call it on a learnpath object.
11053
     *
11054
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11055
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11056
     * path has been modified, it should use the generic method here below.
11057
     *
11058
     * @return string Returns the zip package string, or null if error
11059
     */
11060
    public function scormExport()
11061
    {
11062
        api_set_more_memory_and_time_limits();
11063
11064
        $_course = api_get_course_info();
11065
        $course_id = $_course['real_id'];
11066
        // Create the zip handler (this will remain available throughout the method).
11067
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11068
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11069
        $temp_dir_short = uniqid('scorm_export', true);
11070
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11071
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11072
        $zip_folder = new PclZip($temp_zip_file);
11073
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11074
        $root_path = $main_path = api_get_path(SYS_PATH);
11075
        $files_cleanup = [];
11076
11077
        // Place to temporarily stash the zip file.
11078
        // create the temp dir if it doesn't exist
11079
        // or do a cleanup before creating the zip file.
11080
        if (!is_dir($temp_zip_dir)) {
11081
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11082
        } else {
11083
            // Cleanup: Check the temp dir for old files and delete them.
11084
            $handle = opendir($temp_zip_dir);
11085
            while (false !== ($file = readdir($handle))) {
11086
                if ($file != '.' && $file != '..') {
11087
                    unlink("$temp_zip_dir/$file");
11088
                }
11089
            }
11090
            closedir($handle);
11091
        }
11092
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11093
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11094
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11095
        ) {
11096
            // Remove the possible . at the end of the path.
11097
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11098
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11099
            mkdir(
11100
                $dest_path_to_scorm_folder,
11101
                api_get_permissions_for_new_directories(),
11102
                true
11103
            );
11104
            copyr(
11105
                $current_course_path.'/scorm/'.$this->path,
11106
                $dest_path_to_scorm_folder,
11107
                ['imsmanifest'],
11108
                $zip_files
11109
            );
11110
        }
11111
11112
        // Build a dummy imsmanifest structure.
11113
        // Do not add to the zip yet (we still need it).
11114
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11115
        // Aggregation Model official document, section "2.3 Content Packaging".
11116
        // We are going to build a UTF-8 encoded manifest.
11117
        // Later we will recode it to the desired (and supported) encoding.
11118
        $xmldoc = new DOMDocument('1.0');
11119
        $root = $xmldoc->createElement('manifest');
11120
        $root->setAttribute('identifier', 'SingleCourseManifest');
11121
        $root->setAttribute('version', '1.1');
11122
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11123
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11124
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11125
        $root->setAttribute(
11126
            'xsi:schemaLocation',
11127
            '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'
11128
        );
11129
        // Build mandatory sub-root container elements.
11130
        $metadata = $xmldoc->createElement('metadata');
11131
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11132
        $metadata->appendChild($md_schema);
11133
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11134
        $metadata->appendChild($md_schemaversion);
11135
        $root->appendChild($metadata);
11136
11137
        $organizations = $xmldoc->createElement('organizations');
11138
        $resources = $xmldoc->createElement('resources');
11139
11140
        // Build the only organization we will use in building our learnpaths.
11141
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11142
        $organization = $xmldoc->createElement('organization');
11143
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11144
        // To set the title of the SCORM entity (=organization), we take the name given
11145
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11146
        // learning path charset) as it is the encoding that defines how it is stored
11147
        // in the database. Then we convert it to HTML entities again as the "&" character
11148
        // alone is not authorized in XML (must be &amp;).
11149
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11150
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11151
        $organization->appendChild($org_title);
11152
        $folder_name = 'document';
11153
11154
        // Removes the learning_path/scorm_folder path when exporting see #4841
11155
        $path_to_remove = '';
11156
        $path_to_replace = '';
11157
        $result = $this->generate_lp_folder($_course);
11158
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11159
            $path_to_remove = 'document'.$result['dir'];
11160
            $path_to_replace = $folder_name.'/';
11161
        }
11162
11163
        // Fixes chamilo scorm exports
11164
        if ($this->ref === 'chamilo_scorm_export') {
11165
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11166
        }
11167
11168
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11169
        $link_updates = [];
11170
        $links_to_create = [];
11171
        foreach ($this->ordered_items as $index => $itemId) {
11172
            /** @var learnpathItem $item */
11173
            $item = $this->items[$itemId];
11174
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11175
                // Get included documents from this item.
11176
                if ($item->type === 'sco') {
11177
                    $inc_docs = $item->get_resources_from_source(
11178
                        null,
11179
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11180
                    );
11181
                } else {
11182
                    $inc_docs = $item->get_resources_from_source();
11183
                }
11184
11185
                // Give a child element <item> to the <organization> element.
11186
                $my_item_id = $item->get_id();
11187
                $my_item = $xmldoc->createElement('item');
11188
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11189
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11190
                $my_item->setAttribute('isvisible', 'true');
11191
                // Give a child element <title> to the <item> element.
11192
                $my_title = $xmldoc->createElement(
11193
                    'title',
11194
                    htmlspecialchars(
11195
                        api_utf8_encode($item->get_title()),
11196
                        ENT_QUOTES,
11197
                        'UTF-8'
11198
                    )
11199
                );
11200
                $my_item->appendChild($my_title);
11201
                // Give a child element <adlcp:prerequisites> to the <item> element.
11202
                $my_prereqs = $xmldoc->createElement(
11203
                    'adlcp:prerequisites',
11204
                    $this->get_scorm_prereq_string($my_item_id)
11205
                );
11206
                $my_prereqs->setAttribute('type', 'aicc_script');
11207
                $my_item->appendChild($my_prereqs);
11208
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11209
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11210
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11211
                //$xmldoc->createElement('adlcp:timelimitaction','');
11212
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11213
                //$xmldoc->createElement('adlcp:datafromlms','');
11214
                // Give a child element <adlcp:masteryscore> to the <item> element.
11215
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11216
                $my_item->appendChild($my_masteryscore);
11217
11218
                // Attach this item to the organization element or hits parent if there is one.
11219
                if (!empty($item->parent) && $item->parent != 0) {
11220
                    $children = $organization->childNodes;
11221
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11222
                    if (is_object($possible_parent)) {
11223
                        $possible_parent->appendChild($my_item);
11224
                    } else {
11225
                        if ($this->debug > 0) {
11226
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11227
                        }
11228
                    }
11229
                } else {
11230
                    if ($this->debug > 0) {
11231
                        error_log('No parent');
11232
                    }
11233
                    $organization->appendChild($my_item);
11234
                }
11235
11236
                // Get the path of the file(s) from the course directory root.
11237
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11238
                $my_xml_file_path = $my_file_path;
11239
                if (!empty($path_to_remove)) {
11240
                    // From docs
11241
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11242
11243
                    // From quiz
11244
                    if ($this->ref === 'chamilo_scorm_export') {
11245
                        $path_to_remove = 'scorm/'.$this->path.'/';
11246
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11247
                    }
11248
                }
11249
11250
                $my_sub_dir = dirname($my_file_path);
11251
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11252
                $my_xml_sub_dir = $my_sub_dir;
11253
                // Give a <resource> child to the <resources> element
11254
                $my_resource = $xmldoc->createElement('resource');
11255
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11256
                $my_resource->setAttribute('type', 'webcontent');
11257
                $my_resource->setAttribute('href', $my_xml_file_path);
11258
                // adlcp:scormtype can be either 'sco' or 'asset'.
11259
                if ($item->type === 'sco') {
11260
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11261
                } else {
11262
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11263
                }
11264
                // xml:base is the base directory to find the files declared in this resource.
11265
                $my_resource->setAttribute('xml:base', '');
11266
                // Give a <file> child to the <resource> element.
11267
                $my_file = $xmldoc->createElement('file');
11268
                $my_file->setAttribute('href', $my_xml_file_path);
11269
                $my_resource->appendChild($my_file);
11270
11271
                // Dependency to other files - not yet supported.
11272
                $i = 1;
11273
                if ($inc_docs) {
11274
                    foreach ($inc_docs as $doc_info) {
11275
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11276
                            continue;
11277
                        }
11278
                        $my_dep = $xmldoc->createElement('resource');
11279
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11280
                        $my_dep->setAttribute('identifier', $res_id);
11281
                        $my_dep->setAttribute('type', 'webcontent');
11282
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11283
                        $my_dep_file = $xmldoc->createElement('file');
11284
                        // Check type of URL.
11285
                        if ($doc_info[1] == 'remote') {
11286
                            // Remote file. Save url as is.
11287
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11288
                            $my_dep->setAttribute('xml:base', '');
11289
                        } elseif ($doc_info[1] === 'local') {
11290
                            switch ($doc_info[2]) {
11291
                                case 'url':
11292
                                    // Local URL - save path as url for now, don't zip file.
11293
                                    $abs_path = api_get_path(SYS_PATH).
11294
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11295
                                    $current_dir = dirname($abs_path);
11296
                                    $current_dir = str_replace('\\', '/', $current_dir);
11297
                                    $file_path = realpath($abs_path);
11298
                                    $file_path = str_replace('\\', '/', $file_path);
11299
                                    $my_dep_file->setAttribute('href', $file_path);
11300
                                    $my_dep->setAttribute('xml:base', '');
11301
                                    if (strstr($file_path, $main_path) !== false) {
11302
                                        // The calculated real path is really inside Chamilo's root path.
11303
                                        // Reduce file path to what's under the DocumentRoot.
11304
                                        $replace = $file_path;
11305
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11306
                                        $destinationFile = $file_path;
11307
11308
                                        if (strstr($file_path, 'upload/users') !== false) {
11309
                                            $pos = strpos($file_path, 'my_files/');
11310
                                            if ($pos !== false) {
11311
                                                $onlyDirectory = str_replace(
11312
                                                    'upload/users/',
11313
                                                    '',
11314
                                                    substr($file_path, $pos, strlen($file_path))
11315
                                                );
11316
                                            }
11317
                                            $replace = $onlyDirectory;
11318
                                            $destinationFile = $replace;
11319
                                        }
11320
                                        $zip_files_abs[] = $file_path;
11321
                                        $link_updates[$my_file_path][] = [
11322
                                            'orig' => $doc_info[0],
11323
                                            'dest' => $destinationFile,
11324
                                            'replace' => $replace,
11325
                                        ];
11326
                                        $my_dep_file->setAttribute('href', $file_path);
11327
                                        $my_dep->setAttribute('xml:base', '');
11328
                                    } elseif (empty($file_path)) {
11329
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11330
                                        $file_path = str_replace('//', '/', $file_path);
11331
                                        if (file_exists($file_path)) {
11332
                                            // We get the relative path.
11333
                                            $file_path = substr($file_path, strlen($current_dir));
11334
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11335
                                            $link_updates[$my_file_path][] = [
11336
                                                'orig' => $doc_info[0],
11337
                                                'dest' => $file_path,
11338
                                            ];
11339
                                            $my_dep_file->setAttribute('href', $file_path);
11340
                                            $my_dep->setAttribute('xml:base', '');
11341
                                        }
11342
                                    }
11343
                                    break;
11344
                                case 'abs':
11345
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11346
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11347
                                    $my_dep->setAttribute('xml:base', '');
11348
11349
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11350
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11351
                                    $abs_img_path_without_subdir = $doc_info[0];
11352
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11353
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11354
                                    if ($pos === 0) {
11355
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11356
                                    }
11357
11358
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11359
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11360
11361
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11362
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11363
                                    // Check if the current document is in that path.
11364
                                    if (strstr($file_path, $cur_path) !== false) {
11365
                                        $destinationFile = substr($file_path, strlen($cur_path));
11366
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11367
11368
                                        $fileToTest = $cur_path.$my_file_path;
11369
                                        if (!empty($path_to_remove)) {
11370
                                            $fileToTest = str_replace(
11371
                                                $path_to_remove.'/',
11372
                                                $path_to_replace,
11373
                                                $cur_path.$my_file_path
11374
                                            );
11375
                                        }
11376
11377
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11378
11379
                                        // Put the current document in the zip (this array is the array
11380
                                        // that will manage documents already in the course folder - relative).
11381
                                        $zip_files[] = $filePathNoCoursePart;
11382
                                        // Update the links to the current document in the
11383
                                        // containing document (make them relative).
11384
                                        $link_updates[$my_file_path][] = [
11385
                                            'orig' => $doc_info[0],
11386
                                            'dest' => $destinationFile,
11387
                                            'replace' => $relative_path,
11388
                                        ];
11389
11390
                                        $my_dep_file->setAttribute('href', $file_path);
11391
                                        $my_dep->setAttribute('xml:base', '');
11392
                                    } elseif (strstr($file_path, $main_path) !== false) {
11393
                                        // The calculated real path is really inside Chamilo's root path.
11394
                                        // Reduce file path to what's under the DocumentRoot.
11395
                                        $file_path = substr($file_path, strlen($root_path));
11396
                                        $zip_files_abs[] = $file_path;
11397
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11398
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11399
                                        $my_dep->setAttribute('xml:base', '');
11400
                                    } elseif (empty($file_path)) {
11401
                                        // Probably this is an image inside "/main" directory
11402
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11403
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11404
11405
                                        if (file_exists($file_path)) {
11406
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11407
                                                // We get the relative path.
11408
                                                $pos = strpos($file_path, 'main/default_course_document/');
11409
                                                if ($pos !== false) {
11410
                                                    $onlyDirectory = str_replace(
11411
                                                        'main/default_course_document/',
11412
                                                        '',
11413
                                                        substr($file_path, $pos, strlen($file_path))
11414
                                                    );
11415
                                                }
11416
11417
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11418
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11419
                                                $zip_files_abs[] = $fileAbs;
11420
                                                $link_updates[$my_file_path][] = [
11421
                                                    'orig' => $doc_info[0],
11422
                                                    'dest' => $destinationFile,
11423
                                                ];
11424
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11425
                                                $my_dep->setAttribute('xml:base', '');
11426
                                            }
11427
                                        }
11428
                                    }
11429
                                    break;
11430
                                case 'rel':
11431
                                    // Path relative to the current document.
11432
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11433
                                    if (substr($doc_info[0], 0, 2) === '..') {
11434
                                        // Relative path going up.
11435
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11436
                                        $current_dir = str_replace('\\', '/', $current_dir);
11437
                                        $file_path = realpath($current_dir.$doc_info[0]);
11438
                                        $file_path = str_replace('\\', '/', $file_path);
11439
                                        if (strstr($file_path, $main_path) !== false) {
11440
                                            // The calculated real path is really inside Chamilo's root path.
11441
                                            // Reduce file path to what's under the DocumentRoot.
11442
                                            $file_path = substr($file_path, strlen($root_path));
11443
                                            $zip_files_abs[] = $file_path;
11444
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11445
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11446
                                            $my_dep->setAttribute('xml:base', '');
11447
                                        }
11448
                                    } else {
11449
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11450
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11451
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11452
                                    }
11453
                                    break;
11454
                                default:
11455
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11456
                                    $my_dep->setAttribute('xml:base', '');
11457
                                    break;
11458
                            }
11459
                        }
11460
                        $my_dep->appendChild($my_dep_file);
11461
                        $resources->appendChild($my_dep);
11462
                        $dependency = $xmldoc->createElement('dependency');
11463
                        $dependency->setAttribute('identifierref', $res_id);
11464
                        $my_resource->appendChild($dependency);
11465
                        $i++;
11466
                    }
11467
                }
11468
                $resources->appendChild($my_resource);
11469
                $zip_files[] = $my_file_path;
11470
            } else {
11471
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11472
                switch ($item->type) {
11473
                    case TOOL_LINK:
11474
                        $my_item = $xmldoc->createElement('item');
11475
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11476
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11477
                        $my_item->setAttribute('isvisible', 'true');
11478
                        // Give a child element <title> to the <item> element.
11479
                        $my_title = $xmldoc->createElement(
11480
                            'title',
11481
                            htmlspecialchars(
11482
                                api_utf8_encode($item->get_title()),
11483
                                ENT_QUOTES,
11484
                                'UTF-8'
11485
                            )
11486
                        );
11487
                        $my_item->appendChild($my_title);
11488
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11489
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11490
                        $my_prereqs->setAttribute('type', 'aicc_script');
11491
                        $my_item->appendChild($my_prereqs);
11492
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11493
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11494
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11495
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11496
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11497
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11498
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11499
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11500
                        $my_item->appendChild($my_masteryscore);
11501
11502
                        // Attach this item to the organization element or its parent if there is one.
11503
                        if (!empty($item->parent) && $item->parent != 0) {
11504
                            $children = $organization->childNodes;
11505
                            for ($i = 0; $i < $children->length; $i++) {
11506
                                $item_temp = $children->item($i);
11507
                                if ($item_temp->nodeName == 'item') {
11508
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11509
                                        $item_temp->appendChild($my_item);
11510
                                    }
11511
                                }
11512
                            }
11513
                        } else {
11514
                            $organization->appendChild($my_item);
11515
                        }
11516
11517
                        $my_file_path = 'link_'.$item->get_id().'.html';
11518
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11519
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11520
                        $rs = Database::query($sql);
11521
                        if ($link = Database::fetch_array($rs)) {
11522
                            $url = $link['url'];
11523
                            $title = stripslashes($link['title']);
11524
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11525
                            $my_xml_file_path = $my_file_path;
11526
                            $my_sub_dir = dirname($my_file_path);
11527
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11528
                            $my_xml_sub_dir = $my_sub_dir;
11529
                            // Give a <resource> child to the <resources> element.
11530
                            $my_resource = $xmldoc->createElement('resource');
11531
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11532
                            $my_resource->setAttribute('type', 'webcontent');
11533
                            $my_resource->setAttribute('href', $my_xml_file_path);
11534
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11535
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11536
                            // xml:base is the base directory to find the files declared in this resource.
11537
                            $my_resource->setAttribute('xml:base', '');
11538
                            // give a <file> child to the <resource> element.
11539
                            $my_file = $xmldoc->createElement('file');
11540
                            $my_file->setAttribute('href', $my_xml_file_path);
11541
                            $my_resource->appendChild($my_file);
11542
                            $resources->appendChild($my_resource);
11543
                        }
11544
                        break;
11545
                    case TOOL_QUIZ:
11546
                        $exe_id = $item->path;
11547
                        // Should be using ref when everything will be cleaned up in this regard.
11548
                        $exe = new Exercise();
11549
                        $exe->read($exe_id);
11550
                        $my_item = $xmldoc->createElement('item');
11551
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11552
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11553
                        $my_item->setAttribute('isvisible', 'true');
11554
                        // Give a child element <title> to the <item> element.
11555
                        $my_title = $xmldoc->createElement(
11556
                            'title',
11557
                            htmlspecialchars(
11558
                                api_utf8_encode($item->get_title()),
11559
                                ENT_QUOTES,
11560
                                'UTF-8'
11561
                            )
11562
                        );
11563
                        $my_item->appendChild($my_title);
11564
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11565
                        $my_item->appendChild($my_max_score);
11566
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11567
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11568
                        $my_prereqs->setAttribute('type', 'aicc_script');
11569
                        $my_item->appendChild($my_prereqs);
11570
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11571
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11572
                        $my_item->appendChild($my_masteryscore);
11573
11574
                        // Attach this item to the organization element or hits parent if there is one.
11575
                        if (!empty($item->parent) && $item->parent != 0) {
11576
                            $children = $organization->childNodes;
11577
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11578
                            if ($possible_parent) {
11579
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11580
                                    $possible_parent->appendChild($my_item);
11581
                                }
11582
                            }
11583
                        } else {
11584
                            $organization->appendChild($my_item);
11585
                        }
11586
11587
                        // Get the path of the file(s) from the course directory root
11588
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11589
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11590
                        // Write the contents of the exported exercise into a (big) html file
11591
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11592
                        $scormExercise = new ScormExercise($exe, true);
11593
                        $contents = $scormExercise->export();
11594
11595
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11596
                        $res = file_put_contents($tmp_file_path, $contents);
11597
                        if ($res === false) {
11598
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11599
                        }
11600
                        $files_cleanup[] = $tmp_file_path;
11601
                        $my_xml_file_path = $my_file_path;
11602
                        $my_sub_dir = dirname($my_file_path);
11603
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11604
                        $my_xml_sub_dir = $my_sub_dir;
11605
                        // Give a <resource> child to the <resources> element.
11606
                        $my_resource = $xmldoc->createElement('resource');
11607
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11608
                        $my_resource->setAttribute('type', 'webcontent');
11609
                        $my_resource->setAttribute('href', $my_xml_file_path);
11610
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11611
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11612
                        // xml:base is the base directory to find the files declared in this resource.
11613
                        $my_resource->setAttribute('xml:base', '');
11614
                        // Give a <file> child to the <resource> element.
11615
                        $my_file = $xmldoc->createElement('file');
11616
                        $my_file->setAttribute('href', $my_xml_file_path);
11617
                        $my_resource->appendChild($my_file);
11618
11619
                        // Get included docs.
11620
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11621
11622
                        // Dependency to other files - not yet supported.
11623
                        $i = 1;
11624
                        foreach ($inc_docs as $doc_info) {
11625
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11626
                                continue;
11627
                            }
11628
                            $my_dep = $xmldoc->createElement('resource');
11629
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11630
                            $my_dep->setAttribute('identifier', $res_id);
11631
                            $my_dep->setAttribute('type', 'webcontent');
11632
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11633
                            $my_dep_file = $xmldoc->createElement('file');
11634
                            // Check type of URL.
11635
                            if ($doc_info[1] == 'remote') {
11636
                                // Remote file. Save url as is.
11637
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11638
                                $my_dep->setAttribute('xml:base', '');
11639
                            } elseif ($doc_info[1] == 'local') {
11640
                                switch ($doc_info[2]) {
11641
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11642
                                        // Save file but as local file (retrieve from URL).
11643
                                        $abs_path = api_get_path(SYS_PATH).
11644
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11645
                                        $current_dir = dirname($abs_path);
11646
                                        $current_dir = str_replace('\\', '/', $current_dir);
11647
                                        $file_path = realpath($abs_path);
11648
                                        $file_path = str_replace('\\', '/', $file_path);
11649
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11650
                                        $my_dep->setAttribute('xml:base', '');
11651
                                        if (strstr($file_path, $main_path) !== false) {
11652
                                            // The calculated real path is really inside the chamilo root path.
11653
                                            // Reduce file path to what's under the DocumentRoot.
11654
                                            $file_path = substr($file_path, strlen($root_path));
11655
                                            $zip_files_abs[] = $file_path;
11656
                                            $link_updates[$my_file_path][] = [
11657
                                                'orig' => $doc_info[0],
11658
                                                'dest' => 'document/'.$file_path,
11659
                                            ];
11660
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11661
                                            $my_dep->setAttribute('xml:base', '');
11662
                                        } elseif (empty($file_path)) {
11663
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11664
                                            $file_path = str_replace('//', '/', $file_path);
11665
                                            if (file_exists($file_path)) {
11666
                                                $file_path = substr($file_path, strlen($current_dir));
11667
                                                // We get the relative path.
11668
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11669
                                                $link_updates[$my_file_path][] = [
11670
                                                    'orig' => $doc_info[0],
11671
                                                    'dest' => 'document/'.$file_path,
11672
                                                ];
11673
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11674
                                                $my_dep->setAttribute('xml:base', '');
11675
                                            }
11676
                                        }
11677
                                        break;
11678
                                    case 'abs':
11679
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11680
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11681
                                        $current_dir = str_replace('\\', '/', $current_dir);
11682
                                        $file_path = realpath($doc_info[0]);
11683
                                        $file_path = str_replace('\\', '/', $file_path);
11684
                                        $my_dep_file->setAttribute('href', $file_path);
11685
                                        $my_dep->setAttribute('xml:base', '');
11686
11687
                                        if (strstr($file_path, $main_path) !== false) {
11688
                                            // The calculated real path is really inside the chamilo root path.
11689
                                            // Reduce file path to what's under the DocumentRoot.
11690
                                            $file_path = substr($file_path, strlen($root_path));
11691
                                            $zip_files_abs[] = $file_path;
11692
                                            $link_updates[$my_file_path][] = [
11693
                                                'orig' => $doc_info[0],
11694
                                                'dest' => $file_path,
11695
                                            ];
11696
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11697
                                            $my_dep->setAttribute('xml:base', '');
11698
                                        } elseif (empty($file_path)) {
11699
                                            $docSysPartPath = str_replace(
11700
                                                api_get_path(REL_COURSE_PATH),
11701
                                                '',
11702
                                                $doc_info[0]
11703
                                            );
11704
11705
                                            $docSysPartPathNoCourseCode = str_replace(
11706
                                                $_course['directory'].'/',
11707
                                                '',
11708
                                                $docSysPartPath
11709
                                            );
11710
11711
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11712
                                            if (file_exists($docSysPath)) {
11713
                                                $file_path = $docSysPartPathNoCourseCode;
11714
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11715
                                                $link_updates[$my_file_path][] = [
11716
                                                    'orig' => $doc_info[0],
11717
                                                    'dest' => $file_path,
11718
                                                ];
11719
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11720
                                                $my_dep->setAttribute('xml:base', '');
11721
                                            }
11722
                                        }
11723
                                        break;
11724
                                    case 'rel':
11725
                                        // Path relative to the current document. Save xml:base as current document's
11726
                                        // directory and save file in zip as subdir.file_path
11727
                                        if (substr($doc_info[0], 0, 2) === '..') {
11728
                                            // Relative path going up.
11729
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11730
                                            $current_dir = str_replace('\\', '/', $current_dir);
11731
                                            $file_path = realpath($current_dir.$doc_info[0]);
11732
                                            $file_path = str_replace('\\', '/', $file_path);
11733
                                            //error_log($file_path.' <-> '.$main_path, 0);
11734
                                            if (strstr($file_path, $main_path) !== false) {
11735
                                                // The calculated real path is really inside Chamilo's root path.
11736
                                                // Reduce file path to what's under the DocumentRoot.
11737
11738
                                                $file_path = substr($file_path, strlen($root_path));
11739
                                                $file_path_dest = $file_path;
11740
11741
                                                // File path is courses/CHAMILO/document/....
11742
                                                $info_file_path = explode('/', $file_path);
11743
                                                if ($info_file_path[0] == 'courses') {
11744
                                                    // Add character "/" in file path.
11745
                                                    $file_path_dest = 'document/'.$file_path;
11746
                                                }
11747
                                                $zip_files_abs[] = $file_path;
11748
11749
                                                $link_updates[$my_file_path][] = [
11750
                                                    'orig' => $doc_info[0],
11751
                                                    'dest' => $file_path_dest,
11752
                                                ];
11753
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11754
                                                $my_dep->setAttribute('xml:base', '');
11755
                                            }
11756
                                        } else {
11757
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11758
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11759
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11760
                                        }
11761
                                        break;
11762
                                    default:
11763
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11764
                                        $my_dep->setAttribute('xml:base', '');
11765
                                        break;
11766
                                }
11767
                            }
11768
                            $my_dep->appendChild($my_dep_file);
11769
                            $resources->appendChild($my_dep);
11770
                            $dependency = $xmldoc->createElement('dependency');
11771
                            $dependency->setAttribute('identifierref', $res_id);
11772
                            $my_resource->appendChild($dependency);
11773
                            $i++;
11774
                        }
11775
                        $resources->appendChild($my_resource);
11776
                        $zip_files[] = $my_file_path;
11777
                        break;
11778
                    default:
11779
                        // Get the path of the file(s) from the course directory root
11780
                        $my_file_path = 'non_exportable.html';
11781
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11782
                        $my_xml_file_path = $my_file_path;
11783
                        $my_sub_dir = dirname($my_file_path);
11784
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11785
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11786
                        $my_xml_sub_dir = $my_sub_dir;
11787
                        // Give a <resource> child to the <resources> element.
11788
                        $my_resource = $xmldoc->createElement('resource');
11789
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11790
                        $my_resource->setAttribute('type', 'webcontent');
11791
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11792
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11793
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11794
                        // xml:base is the base directory to find the files declared in this resource.
11795
                        $my_resource->setAttribute('xml:base', '');
11796
                        // Give a <file> child to the <resource> element.
11797
                        $my_file = $xmldoc->createElement('file');
11798
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11799
                        $my_resource->appendChild($my_file);
11800
                        $resources->appendChild($my_resource);
11801
                        break;
11802
                }
11803
            }
11804
        }
11805
        $organizations->appendChild($organization);
11806
        $root->appendChild($organizations);
11807
        $root->appendChild($resources);
11808
        $xmldoc->appendChild($root);
11809
11810
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11811
11812
        // then add the file to the zip, then destroy the file (this is done automatically).
11813
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11814
        foreach ($zip_files as $file_path) {
11815
            if (empty($file_path)) {
11816
                continue;
11817
            }
11818
11819
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11820
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11821
11822
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11823
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11824
            }
11825
11826
            $this->create_path($dest_file);
11827
            @copy($filePath, $dest_file);
11828
11829
            // Check if the file needs a link update.
11830
            if (in_array($file_path, array_keys($link_updates))) {
11831
                $string = file_get_contents($dest_file);
11832
                unlink($dest_file);
11833
                foreach ($link_updates[$file_path] as $old_new) {
11834
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11835
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11836
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11837
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11838
                    if (substr($old_new['dest'], -3) === 'flv' &&
11839
                        substr($old_new['dest'], 0, 5) === 'main/'
11840
                    ) {
11841
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11842
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11843
                        substr($old_new['dest'], 0, 6) === 'video/'
11844
                    ) {
11845
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11846
                    }
11847
11848
                    // Fix to avoid problems with default_course_document
11849
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11850
                        $newDestination = $old_new['dest'];
11851
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11852
                            $newDestination = $old_new['replace'];
11853
                        }
11854
                    } else {
11855
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11856
                    }
11857
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11858
11859
                    // Add files inside the HTMLs
11860
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11861
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11862
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11863
                        copy(
11864
                            $sys_course_path.$new_path,
11865
                            $destinationFile
11866
                        );
11867
                    }
11868
                }
11869
                file_put_contents($dest_file, $string);
11870
            }
11871
11872
            if (file_exists($filePath) && $copyAll) {
11873
                $extension = $this->get_extension($filePath);
11874
                if (in_array($extension, ['html', 'html'])) {
11875
                    $containerOrigin = dirname($filePath);
11876
                    $containerDestination = dirname($dest_file);
11877
11878
                    $finder = new Finder();
11879
                    $finder->files()->in($containerOrigin)
11880
                        ->notName('*_DELETED_*')
11881
                        ->exclude('share_folder')
11882
                        ->exclude('chat_files')
11883
                        ->exclude('certificates')
11884
                    ;
11885
11886
                    if (is_dir($containerOrigin) &&
11887
                        is_dir($containerDestination)
11888
                    ) {
11889
                        $fs = new Filesystem();
11890
                        $fs->mirror(
11891
                            $containerOrigin,
11892
                            $containerDestination,
11893
                            $finder
11894
                        );
11895
                    }
11896
                }
11897
            }
11898
        }
11899
11900
        foreach ($zip_files_abs as $file_path) {
11901
            if (empty($file_path)) {
11902
                continue;
11903
            }
11904
11905
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11906
                continue;
11907
            }
11908
11909
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11910
            if (strstr($file_path, 'upload/users') !== false) {
11911
                $pos = strpos($file_path, 'my_files/');
11912
                if ($pos !== false) {
11913
                    $onlyDirectory = str_replace(
11914
                        'upload/users/',
11915
                        '',
11916
                        substr($file_path, $pos, strlen($file_path))
11917
                    );
11918
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11919
                }
11920
            }
11921
11922
            if (strstr($file_path, 'default_course_document/') !== false) {
11923
                $replace = str_replace('/main', '', $file_path);
11924
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11925
            }
11926
11927
            if (empty($dest_file)) {
11928
                continue;
11929
            }
11930
11931
            $this->create_path($dest_file);
11932
            copy($main_path.$file_path, $dest_file);
11933
            // Check if the file needs a link update.
11934
            if (in_array($file_path, array_keys($link_updates))) {
11935
                $string = file_get_contents($dest_file);
11936
                unlink($dest_file);
11937
                foreach ($link_updates[$file_path] as $old_new) {
11938
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11939
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11940
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11941
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11942
                    if (substr($old_new['dest'], -3) == 'flv' &&
11943
                        substr($old_new['dest'], 0, 5) == 'main/'
11944
                    ) {
11945
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11946
                    }
11947
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11948
                }
11949
                file_put_contents($dest_file, $string);
11950
            }
11951
        }
11952
11953
        if (is_array($links_to_create)) {
11954
            foreach ($links_to_create as $file => $link) {
11955
                $content = '<!DOCTYPE html><head>
11956
                            <meta charset="'.api_get_language_isocode().'" />
11957
                            <title>'.$link['title'].'</title>
11958
                            </head>
11959
                            <body dir="'.api_get_text_direction().'">
11960
                            <div style="text-align:center">
11961
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11962
                            </body>
11963
                            </html>';
11964
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11965
            }
11966
        }
11967
11968
        // Add non exportable message explanation.
11969
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11970
        $file_content = '<!DOCTYPE html><head>
11971
                        <meta charset="'.api_get_language_isocode().'" />
11972
                        <title>'.$lang_not_exportable.'</title>
11973
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11974
                        </head>
11975
                        <body dir="'.api_get_text_direction().'">';
11976
        $file_content .=
11977
            <<<EOD
11978
                    <style>
11979
            .error-message {
11980
                font-family: arial, verdana, helvetica, sans-serif;
11981
                border-width: 1px;
11982
                border-style: solid;
11983
                left: 50%;
11984
                margin: 10px auto;
11985
                min-height: 30px;
11986
                padding: 5px;
11987
                right: 50%;
11988
                width: 500px;
11989
                background-color: #FFD1D1;
11990
                border-color: #FF0000;
11991
                color: #000;
11992
            }
11993
        </style>
11994
    <body>
11995
        <div class="error-message">
11996
            $lang_not_exportable
11997
        </div>
11998
    </body>
11999
</html>
12000
EOD;
12001
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
12002
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
12003
        }
12004
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
12005
12006
        // Add the extra files that go along with a SCORM package.
12007
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
12008
12009
        $fs = new Filesystem();
12010
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
12011
12012
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
12013
        $manifest = @$xmldoc->saveXML();
12014
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
12015
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
12016
        $zip_folder->add(
12017
            $archivePath.'/'.$temp_dir_short,
12018
            PCLZIP_OPT_REMOVE_PATH,
12019
            $archivePath.'/'.$temp_dir_short.'/'
12020
        );
12021
12022
        // Clean possible temporary files.
12023
        foreach ($files_cleanup as $file) {
12024
            $res = unlink($file);
12025
            if ($res === false) {
12026
                error_log(
12027
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
12028
                    0
12029
                );
12030
            }
12031
        }
12032
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
12033
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
12034
    }
12035
12036
    /**
12037
     * @param int $lp_id
12038
     *
12039
     * @return bool
12040
     */
12041
    public function scorm_export_to_pdf($lp_id)
12042
    {
12043
        $lp_id = (int) $lp_id;
12044
        $files_to_export = [];
12045
12046
        $sessionId = api_get_session_id();
12047
        $course_data = api_get_course_info($this->cc);
12048
12049
        if (!empty($course_data)) {
12050
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12051
            $list = self::get_flat_ordered_items_list($lp_id);
12052
            if (!empty($list)) {
12053
                foreach ($list as $item_id) {
12054
                    $item = $this->items[$item_id];
12055
                    switch ($item->type) {
12056
                        case 'document':
12057
                            // Getting documents from a LP with chamilo documents
12058
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12059
                            // Try loading document from the base course.
12060
                            if (empty($file_data) && !empty($sessionId)) {
12061
                                $file_data = DocumentManager::get_document_data_by_id(
12062
                                    $item->path,
12063
                                    $this->cc,
12064
                                    false,
12065
                                    0
12066
                                );
12067
                            }
12068
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12069
                            if (file_exists($file_path)) {
12070
                                $files_to_export[] = [
12071
                                    'title' => $item->get_title(),
12072
                                    'path' => $file_path,
12073
                                ];
12074
                            }
12075
                            break;
12076
                        case 'asset': //commes from a scorm package generated by chamilo
12077
                        case 'sco':
12078
                            $file_path = $scorm_path.'/'.$item->path;
12079
                            if (file_exists($file_path)) {
12080
                                $files_to_export[] = [
12081
                                    'title' => $item->get_title(),
12082
                                    'path' => $file_path,
12083
                                ];
12084
                            }
12085
                            break;
12086
                        case 'dir':
12087
                            $files_to_export[] = [
12088
                                'title' => $item->get_title(),
12089
                                'path' => null,
12090
                            ];
12091
                            break;
12092
                    }
12093
                }
12094
            }
12095
12096
            $pdf = new PDF();
12097
            $result = $pdf->html_to_pdf(
12098
                $files_to_export,
12099
                $this->name,
12100
                $this->cc,
12101
                true,
12102
                true,
12103
                true,
12104
                $this->get_name()
12105
            );
12106
12107
            return $result;
12108
        }
12109
12110
        return false;
12111
    }
12112
12113
    /**
12114
     * Temp function to be moved in main_api or the best place around for this.
12115
     * Creates a file path if it doesn't exist.
12116
     *
12117
     * @param string $path
12118
     */
12119
    public function create_path($path)
12120
    {
12121
        $path_bits = explode('/', dirname($path));
12122
12123
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12124
        $path_built = IS_WINDOWS_OS ? '' : '/';
12125
        foreach ($path_bits as $bit) {
12126
            if (!empty($bit)) {
12127
                $new_path = $path_built.$bit;
12128
                if (is_dir($new_path)) {
12129
                    $path_built = $new_path.'/';
12130
                } else {
12131
                    mkdir($new_path, api_get_permissions_for_new_directories());
12132
                    $path_built = $new_path.'/';
12133
                }
12134
            }
12135
        }
12136
    }
12137
12138
    /**
12139
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12140
     *
12141
     * @return bool The results of the unlink function, or false if there was no image to start with
12142
     */
12143
    public function delete_lp_image()
12144
    {
12145
        $img = $this->get_preview_image();
12146
        if ($img != '') {
12147
            $del_file = $this->get_preview_image_path(null, 'sys');
12148
            if (isset($del_file) && file_exists($del_file)) {
12149
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12150
                if (file_exists($del_file_2)) {
12151
                    unlink($del_file_2);
12152
                }
12153
                $this->set_preview_image('');
12154
12155
                return @unlink($del_file);
12156
            }
12157
        }
12158
12159
        return false;
12160
    }
12161
12162
    /**
12163
     * Uploads an author image to the upload/learning_path/images directory.
12164
     *
12165
     * @param array    The image array, coming from the $_FILES superglobal
12166
     *
12167
     * @return bool True on success, false on error
12168
     */
12169
    public function upload_image($image_array)
12170
    {
12171
        if (!empty($image_array['name'])) {
12172
            $upload_ok = process_uploaded_file($image_array);
12173
            $has_attachment = true;
12174
        }
12175
12176
        if ($upload_ok && $has_attachment) {
12177
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12178
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12179
            $updir = $sys_course_path.$courseDir;
12180
            // Try to add an extension to the file if it hasn't one.
12181
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12182
12183
            if (filter_extension($new_file_name)) {
12184
                $file_extension = explode('.', $image_array['name']);
12185
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12186
                $filename = uniqid('');
12187
                $new_file_name = $filename.'.'.$file_extension;
12188
                $new_path = $updir.'/'.$new_file_name;
12189
12190
                // Resize the image.
12191
                $temp = new Image($image_array['tmp_name']);
12192
                $temp->resize(104);
12193
                $result = $temp->send_image($new_path);
12194
12195
                // Storing the image filename.
12196
                if ($result) {
12197
                    $this->set_preview_image($new_file_name);
12198
12199
                    //Resize to 64px to use on course homepage
12200
                    $temp->resize(64);
12201
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12202
12203
                    return true;
12204
                }
12205
            }
12206
        }
12207
12208
        return false;
12209
    }
12210
12211
    /**
12212
     * @param int    $lp_id
12213
     * @param string $status
12214
     */
12215
    public function set_autolaunch($lp_id, $status)
12216
    {
12217
        $course_id = api_get_course_int_id();
12218
        $lp_id = (int) $lp_id;
12219
        $status = (int) $status;
12220
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12221
12222
        // Setting everything to autolaunch = 0
12223
        $attributes['autolaunch'] = 0;
12224
        $where = [
12225
            'session_id = ? AND c_id = ? ' => [
12226
                api_get_session_id(),
12227
                $course_id,
12228
            ],
12229
        ];
12230
        Database::update($lp_table, $attributes, $where);
12231
        if ($status == 1) {
12232
            //Setting my lp_id to autolaunch = 1
12233
            $attributes['autolaunch'] = 1;
12234
            $where = [
12235
                'iid = ? AND session_id = ? AND c_id = ?' => [
12236
                    $lp_id,
12237
                    api_get_session_id(),
12238
                    $course_id,
12239
                ],
12240
            ];
12241
            Database::update($lp_table, $attributes, $where);
12242
        }
12243
    }
12244
12245
    /**
12246
     * Gets previous_item_id for the next element of the lp_item table.
12247
     *
12248
     * @author Isaac flores paz
12249
     *
12250
     * @return int Previous item ID
12251
     */
12252
    public function select_previous_item_id()
12253
    {
12254
        $course_id = api_get_course_int_id();
12255
        if ($this->debug > 0) {
12256
            error_log('In learnpath::select_previous_item_id()', 0);
12257
        }
12258
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12259
12260
        // Get the max order of the items
12261
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12262
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12263
        $rs_max_order = Database::query($sql);
12264
        $row_max_order = Database::fetch_object($rs_max_order);
12265
        $max_order = $row_max_order->display_order;
12266
        // Get the previous item ID
12267
        $sql = "SELECT iid as previous FROM $table_lp_item
12268
                WHERE 
12269
                    c_id = $course_id AND 
12270
                    lp_id = ".$this->lp_id." AND 
12271
                    display_order = '$max_order' ";
12272
        $rs_max = Database::query($sql);
12273
        $row_max = Database::fetch_object($rs_max);
12274
12275
        // Return the previous item ID
12276
        return $row_max->previous;
12277
    }
12278
12279
    /**
12280
     * Copies an LP.
12281
     */
12282
    public function copy()
12283
    {
12284
        // Course builder
12285
        $cb = new CourseBuilder();
12286
12287
        //Setting tools that will be copied
12288
        $cb->set_tools_to_build(['learnpaths']);
12289
12290
        //Setting elements that will be copied
12291
        $cb->set_tools_specific_id_list(
12292
            ['learnpaths' => [$this->lp_id]]
12293
        );
12294
12295
        $course = $cb->build();
12296
12297
        //Course restorer
12298
        $course_restorer = new CourseRestorer($course);
12299
        $course_restorer->set_add_text_in_items(true);
12300
        $course_restorer->set_tool_copy_settings(
12301
            ['learnpaths' => ['reset_dates' => true]]
12302
        );
12303
        $course_restorer->restore(
12304
            api_get_course_id(),
12305
            api_get_session_id(),
12306
            false,
12307
            false
12308
        );
12309
    }
12310
12311
    /**
12312
     * Verify document size.
12313
     *
12314
     * @param string $s
12315
     *
12316
     * @return bool
12317
     */
12318
    public static function verify_document_size($s)
12319
    {
12320
        $post_max = ini_get('post_max_size');
12321
        if (substr($post_max, -1, 1) == 'M') {
12322
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12323
        } elseif (substr($post_max, -1, 1) == 'G') {
12324
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12325
        }
12326
        $upl_max = ini_get('upload_max_filesize');
12327
        if (substr($upl_max, -1, 1) == 'M') {
12328
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12329
        } elseif (substr($upl_max, -1, 1) == 'G') {
12330
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12331
        }
12332
        $documents_total_space = DocumentManager::documents_total_space();
12333
        $course_max_space = DocumentManager::get_course_quota();
12334
        $total_size = filesize($s) + $documents_total_space;
12335
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12336
            return true;
12337
        } else {
12338
            return false;
12339
        }
12340
    }
12341
12342
    /**
12343
     * Clear LP prerequisites.
12344
     */
12345
    public function clear_prerequisites()
12346
    {
12347
        $course_id = $this->get_course_int_id();
12348
        if ($this->debug > 0) {
12349
            error_log('In learnpath::clear_prerequisites()', 0);
12350
        }
12351
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12352
        $lp_id = $this->get_id();
12353
        //Cleaning prerequisites
12354
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12355
                WHERE c_id = $course_id AND lp_id = $lp_id";
12356
        Database::query($sql);
12357
12358
        //Cleaning mastery score for exercises
12359
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12360
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12361
        Database::query($sql);
12362
    }
12363
12364
    public function set_previous_step_as_prerequisite_for_all_items()
12365
    {
12366
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12367
        $course_id = $this->get_course_int_id();
12368
        $lp_id = $this->get_id();
12369
12370
        if (!empty($this->items)) {
12371
            $previous_item_id = null;
12372
            $previous_item_max = 0;
12373
            $previous_item_type = null;
12374
            $last_item_not_dir = null;
12375
            $last_item_not_dir_type = null;
12376
            $last_item_not_dir_max = null;
12377
12378
            foreach ($this->ordered_items as $itemId) {
12379
                $item = $this->getItem($itemId);
12380
                // if there was a previous item... (otherwise jump to set it)
12381
                if (!empty($previous_item_id)) {
12382
                    $current_item_id = $item->get_id(); //save current id
12383
                    if ($item->get_type() != 'dir') {
12384
                        // Current item is not a folder, so it qualifies to get a prerequisites
12385
                        if ($last_item_not_dir_type == 'quiz') {
12386
                            // if previous is quiz, mark its max score as default score to be achieved
12387
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12388
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12389
                            Database::query($sql);
12390
                        }
12391
                        // now simply update the prerequisite to set it to the last non-chapter item
12392
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12393
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12394
                        Database::query($sql);
12395
                        // record item as 'non-chapter' reference
12396
                        $last_item_not_dir = $item->get_id();
12397
                        $last_item_not_dir_type = $item->get_type();
12398
                        $last_item_not_dir_max = $item->get_max();
12399
                    }
12400
                } else {
12401
                    if ($item->get_type() != 'dir') {
12402
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12403
                        $last_item_not_dir = $item->get_id();
12404
                        $last_item_not_dir_type = $item->get_type();
12405
                        $last_item_not_dir_max = $item->get_max();
12406
                    }
12407
                }
12408
                // Saving the item as "previous item" for the next loop
12409
                $previous_item_id = $item->get_id();
12410
                $previous_item_max = $item->get_max();
12411
                $previous_item_type = $item->get_type();
12412
            }
12413
        }
12414
    }
12415
12416
    /**
12417
     * @param array $params
12418
     *
12419
     * @throws \Doctrine\ORM\OptimisticLockException
12420
     *
12421
     * @return int
12422
     */
12423
    public static function createCategory($params)
12424
    {
12425
        $em = Database::getManager();
12426
        $item = new CLpCategory();
12427
        $item->setName($params['name']);
12428
        $item->setCId($params['c_id']);
12429
        $em->persist($item);
12430
        $em->flush();
12431
12432
        api_item_property_update(
12433
            api_get_course_info(),
12434
            TOOL_LEARNPATH_CATEGORY,
12435
            $item->getId(),
12436
            'visible',
12437
            api_get_user_id()
12438
        );
12439
12440
        return $item->getId();
12441
    }
12442
12443
    /**
12444
     * @param array $params
12445
     *
12446
     * @throws \Doctrine\ORM\ORMException
12447
     * @throws \Doctrine\ORM\OptimisticLockException
12448
     * @throws \Doctrine\ORM\TransactionRequiredException
12449
     */
12450
    public static function updateCategory($params)
12451
    {
12452
        $em = Database::getManager();
12453
        /** @var CLpCategory $item */
12454
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12455
        if ($item) {
12456
            $item->setName($params['name']);
12457
            $em->merge($item);
12458
            $em->flush();
12459
        }
12460
    }
12461
12462
    /**
12463
     * @param int $id
12464
     *
12465
     * @throws \Doctrine\ORM\ORMException
12466
     * @throws \Doctrine\ORM\OptimisticLockException
12467
     * @throws \Doctrine\ORM\TransactionRequiredException
12468
     */
12469
    public static function moveUpCategory($id)
12470
    {
12471
        $id = (int) $id;
12472
        $em = Database::getManager();
12473
        /** @var CLpCategory $item */
12474
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12475
        if ($item) {
12476
            $position = $item->getPosition() - 1;
12477
            $item->setPosition($position);
12478
            $em->persist($item);
12479
            $em->flush();
12480
        }
12481
    }
12482
12483
    /**
12484
     * @param int $id
12485
     *
12486
     * @throws \Doctrine\ORM\ORMException
12487
     * @throws \Doctrine\ORM\OptimisticLockException
12488
     * @throws \Doctrine\ORM\TransactionRequiredException
12489
     */
12490
    public static function moveDownCategory($id)
12491
    {
12492
        $id = (int) $id;
12493
        $em = Database::getManager();
12494
        /** @var CLpCategory $item */
12495
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12496
        if ($item) {
12497
            $position = $item->getPosition() + 1;
12498
            $item->setPosition($position);
12499
            $em->persist($item);
12500
            $em->flush();
12501
        }
12502
    }
12503
12504
    /**
12505
     * @param int $courseId
12506
     *
12507
     * @throws \Doctrine\ORM\Query\QueryException
12508
     *
12509
     * @return int|mixed
12510
     */
12511
    public static function getCountCategories($courseId)
12512
    {
12513
        if (empty($courseId)) {
12514
            return 0;
12515
        }
12516
        $em = Database::getManager();
12517
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12518
        $query->setParameter('id', $courseId);
12519
12520
        return $query->getSingleScalarResult();
12521
    }
12522
12523
    /**
12524
     * @param int $courseId
12525
     *
12526
     * @return mixed
12527
     */
12528
    public static function getCategories($courseId)
12529
    {
12530
        $em = Database::getManager();
12531
12532
        // Using doctrine extensions
12533
        /** @var SortableRepository $repo */
12534
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12535
        $items = $repo
12536
            ->getBySortableGroupsQuery(['cId' => $courseId])
12537
            ->getResult();
12538
12539
        return $items;
12540
    }
12541
12542
    /**
12543
     * @param int $id
12544
     *
12545
     * @throws \Doctrine\ORM\ORMException
12546
     * @throws \Doctrine\ORM\OptimisticLockException
12547
     * @throws \Doctrine\ORM\TransactionRequiredException
12548
     *
12549
     * @return CLpCategory
12550
     */
12551
    public static function getCategory($id)
12552
    {
12553
        $id = (int) $id;
12554
        $em = Database::getManager();
12555
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12556
12557
        return $item;
12558
    }
12559
12560
    /**
12561
     * @param int $courseId
12562
     *
12563
     * @return array
12564
     */
12565
    public static function getCategoryByCourse($courseId)
12566
    {
12567
        $em = Database::getManager();
12568
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12569
            ['cId' => $courseId]
12570
        );
12571
12572
        return $items;
12573
    }
12574
12575
    /**
12576
     * @param int $id
12577
     *
12578
     * @throws \Doctrine\ORM\ORMException
12579
     * @throws \Doctrine\ORM\OptimisticLockException
12580
     * @throws \Doctrine\ORM\TransactionRequiredException
12581
     *
12582
     * @return mixed
12583
     */
12584
    public static function deleteCategory($id)
12585
    {
12586
        $em = Database::getManager();
12587
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12588
        if ($item) {
12589
            $courseId = $item->getCId();
12590
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12591
            $query->setParameter('id', $courseId);
12592
            $query->setParameter('catId', $item->getId());
12593
            $lps = $query->getResult();
12594
12595
            // Setting category = 0.
12596
            if ($lps) {
12597
                foreach ($lps as $lpItem) {
12598
                    $lpItem->setCategoryId(0);
12599
                }
12600
            }
12601
12602
            // Removing category.
12603
            $em->remove($item);
12604
            $em->flush();
12605
12606
            $courseInfo = api_get_course_info_by_id($courseId);
12607
            $sessionId = api_get_session_id();
12608
12609
            // Delete link tool
12610
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12611
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12612
            // Delete tools
12613
            $sql = "DELETE FROM $tbl_tool
12614
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12615
            Database::query($sql);
12616
12617
            return true;
12618
        }
12619
12620
        return false;
12621
    }
12622
12623
    /**
12624
     * @param int  $courseId
12625
     * @param bool $addSelectOption
12626
     *
12627
     * @return mixed
12628
     */
12629
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12630
    {
12631
        $items = self::getCategoryByCourse($courseId);
12632
        $cats = [];
12633
        if ($addSelectOption) {
12634
            $cats = [get_lang('SelectACategory')];
12635
        }
12636
12637
        if (!empty($items)) {
12638
            foreach ($items as $cat) {
12639
                $cats[$cat->getId()] = $cat->getName();
12640
            }
12641
        }
12642
12643
        return $cats;
12644
    }
12645
12646
    /**
12647
     * @param string $courseCode
12648
     * @param int    $lpId
12649
     * @param int    $user_id
12650
     *
12651
     * @return learnpath
12652
     */
12653
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12654
    {
12655
        $debug = 0;
12656
        $learnPath = null;
12657
        $lpObject = Session::read('lpobject');
12658
        if ($lpObject !== null) {
12659
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12660
            if ($debug) {
12661
                error_log('getLpFromSession: unserialize');
12662
                error_log('------getLpFromSession------');
12663
                error_log('------unserialize------');
12664
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12665
                error_log("api_get_sessionid: ".api_get_session_id());
12666
            }
12667
        }
12668
12669
        if (!is_object($learnPath)) {
12670
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12671
            if ($debug) {
12672
                error_log('------getLpFromSession------');
12673
                error_log('getLpFromSession: create new learnpath');
12674
                error_log("create new LP with $courseCode - $lpId - $user_id");
12675
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12676
                error_log("api_get_sessionid: ".api_get_session_id());
12677
            }
12678
        }
12679
12680
        return $learnPath;
12681
    }
12682
12683
    /**
12684
     * @param int $itemId
12685
     *
12686
     * @return learnpathItem|false
12687
     */
12688
    public function getItem($itemId)
12689
    {
12690
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12691
            return $this->items[$itemId];
12692
        }
12693
12694
        return false;
12695
    }
12696
12697
    /**
12698
     * @return int
12699
     */
12700
    public function getCategoryId()
12701
    {
12702
        return (int) $this->categoryId;
12703
    }
12704
12705
    /**
12706
     * @param int $categoryId
12707
     *
12708
     * @return bool
12709
     */
12710
    public function setCategoryId($categoryId)
12711
    {
12712
        $this->categoryId = (int) $categoryId;
12713
        $table = Database::get_course_table(TABLE_LP_MAIN);
12714
        $lp_id = $this->get_id();
12715
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12716
                WHERE iid = $lp_id";
12717
        Database::query($sql);
12718
12719
        return true;
12720
    }
12721
12722
    /**
12723
     * Get whether this is a learning path with the possibility to subscribe
12724
     * users or not.
12725
     *
12726
     * @return int
12727
     */
12728
    public function getSubscribeUsers()
12729
    {
12730
        return $this->subscribeUsers;
12731
    }
12732
12733
    /**
12734
     * Set whether this is a learning path with the possibility to subscribe
12735
     * users or not.
12736
     *
12737
     * @param int $value (0 = false, 1 = true)
12738
     *
12739
     * @return bool
12740
     */
12741
    public function setSubscribeUsers($value)
12742
    {
12743
        if ($this->debug > 0) {
12744
            error_log('In learnpath::set_subscribe_users()', 0);
12745
        }
12746
        $this->subscribeUsers = (int) $value;
12747
        $table = Database::get_course_table(TABLE_LP_MAIN);
12748
        $lp_id = $this->get_id();
12749
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12750
                WHERE iid = $lp_id";
12751
        Database::query($sql);
12752
12753
        return true;
12754
    }
12755
12756
    /**
12757
     * Calculate the count of stars for a user in this LP
12758
     * This calculation is based on the following rules:
12759
     * - the student gets one star when he gets to 50% of the learning path
12760
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12761
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12762
     * - the student gets the final star when the score for the *last* test is >= 80%.
12763
     *
12764
     * @param int $sessionId Optional. The session ID
12765
     *
12766
     * @return int The count of stars
12767
     */
12768
    public function getCalculateStars($sessionId = 0)
12769
    {
12770
        $stars = 0;
12771
        $progress = self::getProgress(
12772
            $this->lp_id,
12773
            $this->user_id,
12774
            $this->course_int_id,
12775
            $sessionId
12776
        );
12777
12778
        if ($progress >= 50) {
12779
            $stars++;
12780
        }
12781
12782
        // Calculate stars chapters evaluation
12783
        $exercisesItems = $this->getExercisesItems();
12784
12785
        if (!empty($exercisesItems)) {
12786
            $totalResult = 0;
12787
12788
            foreach ($exercisesItems as $exerciseItem) {
12789
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12790
                    $this->user_id,
12791
                    $exerciseItem->path,
12792
                    $this->course_int_id,
12793
                    $sessionId,
12794
                    $this->lp_id,
12795
                    $exerciseItem->db_id
12796
                );
12797
12798
                $exerciseResultInfo = end($exerciseResultInfo);
12799
12800
                if (!$exerciseResultInfo) {
12801
                    continue;
12802
                }
12803
12804
                if (!empty($exerciseResultInfo['max_score'])) {
12805
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12806
                } else {
12807
                    $exerciseResult = 0;
12808
                }
12809
                $totalResult += $exerciseResult;
12810
            }
12811
12812
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12813
12814
            if ($totalExerciseAverage >= 50) {
12815
                $stars++;
12816
            }
12817
12818
            if ($totalExerciseAverage >= 80) {
12819
                $stars++;
12820
            }
12821
        }
12822
12823
        // Calculate star for final evaluation
12824
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12825
12826
        if (!empty($finalEvaluationItem)) {
12827
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12828
                $this->user_id,
12829
                $finalEvaluationItem->path,
12830
                $this->course_int_id,
12831
                $sessionId,
12832
                $this->lp_id,
12833
                $finalEvaluationItem->db_id
12834
            );
12835
12836
            $evaluationResultInfo = end($evaluationResultInfo);
12837
12838
            if ($evaluationResultInfo) {
12839
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12840
12841
                if ($evaluationResult >= 80) {
12842
                    $stars++;
12843
                }
12844
            }
12845
        }
12846
12847
        return $stars;
12848
    }
12849
12850
    /**
12851
     * Get the items of exercise type.
12852
     *
12853
     * @return array The items. Otherwise return false
12854
     */
12855
    public function getExercisesItems()
12856
    {
12857
        $exercises = [];
12858
        foreach ($this->items as $item) {
12859
            if ($item->type != 'quiz') {
12860
                continue;
12861
            }
12862
            $exercises[] = $item;
12863
        }
12864
12865
        array_pop($exercises);
12866
12867
        return $exercises;
12868
    }
12869
12870
    /**
12871
     * Get the item of exercise type (evaluation type).
12872
     *
12873
     * @return array The final evaluation. Otherwise return false
12874
     */
12875
    public function getFinalEvaluationItem()
12876
    {
12877
        $exercises = [];
12878
        foreach ($this->items as $item) {
12879
            if ($item->type != 'quiz') {
12880
                continue;
12881
            }
12882
12883
            $exercises[] = $item;
12884
        }
12885
12886
        return array_pop($exercises);
12887
    }
12888
12889
    /**
12890
     * Calculate the total points achieved for the current user in this learning path.
12891
     *
12892
     * @param int $sessionId Optional. The session Id
12893
     *
12894
     * @return int
12895
     */
12896
    public function getCalculateScore($sessionId = 0)
12897
    {
12898
        // Calculate stars chapters evaluation
12899
        $exercisesItems = $this->getExercisesItems();
12900
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12901
        $totalExercisesResult = 0;
12902
        $totalEvaluationResult = 0;
12903
12904
        if ($exercisesItems !== false) {
12905
            foreach ($exercisesItems as $exerciseItem) {
12906
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12907
                    $this->user_id,
12908
                    $exerciseItem->path,
12909
                    $this->course_int_id,
12910
                    $sessionId,
12911
                    $this->lp_id,
12912
                    $exerciseItem->db_id
12913
                );
12914
12915
                $exerciseResultInfo = end($exerciseResultInfo);
12916
12917
                if (!$exerciseResultInfo) {
12918
                    continue;
12919
                }
12920
12921
                $totalExercisesResult += $exerciseResultInfo['score'];
12922
            }
12923
        }
12924
12925
        if (!empty($finalEvaluationItem)) {
12926
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12927
                $this->user_id,
12928
                $finalEvaluationItem->path,
12929
                $this->course_int_id,
12930
                $sessionId,
12931
                $this->lp_id,
12932
                $finalEvaluationItem->db_id
12933
            );
12934
12935
            $evaluationResultInfo = end($evaluationResultInfo);
12936
12937
            if ($evaluationResultInfo) {
12938
                $totalEvaluationResult += $evaluationResultInfo['score'];
12939
            }
12940
        }
12941
12942
        return $totalExercisesResult + $totalEvaluationResult;
12943
    }
12944
12945
    /**
12946
     * Check if URL is not allowed to be show in a iframe.
12947
     *
12948
     * @param string $src
12949
     *
12950
     * @return string
12951
     */
12952
    public function fixBlockedLinks($src)
12953
    {
12954
        $urlInfo = parse_url($src);
12955
12956
        $platformProtocol = 'https';
12957
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12958
            $platformProtocol = 'http';
12959
        }
12960
12961
        $protocolFixApplied = false;
12962
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12963
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12964
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12965
12966
        if ($platformProtocol != $scheme) {
12967
            Session::write('x_frame_source', $src);
12968
            $src = 'blank.php?error=x_frames_options';
12969
            $protocolFixApplied = true;
12970
        }
12971
12972
        if ($protocolFixApplied == false) {
12973
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12974
                // Check X-Frame-Options
12975
                $ch = curl_init();
12976
                $options = [
12977
                    CURLOPT_URL => $src,
12978
                    CURLOPT_RETURNTRANSFER => true,
12979
                    CURLOPT_HEADER => true,
12980
                    CURLOPT_FOLLOWLOCATION => true,
12981
                    CURLOPT_ENCODING => "",
12982
                    CURLOPT_AUTOREFERER => true,
12983
                    CURLOPT_CONNECTTIMEOUT => 120,
12984
                    CURLOPT_TIMEOUT => 120,
12985
                    CURLOPT_MAXREDIRS => 10,
12986
                ];
12987
12988
                $proxySettings = api_get_configuration_value('proxy_settings');
12989
                if (!empty($proxySettings) &&
12990
                    isset($proxySettings['curl_setopt_array'])
12991
                ) {
12992
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12993
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12994
                }
12995
12996
                curl_setopt_array($ch, $options);
12997
                $response = curl_exec($ch);
12998
                $httpCode = curl_getinfo($ch);
12999
                $headers = substr($response, 0, $httpCode['header_size']);
13000
13001
                $error = false;
13002
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
13003
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
13004
                ) {
13005
                    $error = true;
13006
                }
13007
13008
                if ($error) {
13009
                    Session::write('x_frame_source', $src);
13010
                    $src = 'blank.php?error=x_frames_options';
13011
                }
13012
            }
13013
        }
13014
13015
        return $src;
13016
    }
13017
13018
    /**
13019
     * Check if this LP has a created forum in the basis course.
13020
     *
13021
     * @return bool
13022
     */
13023
    public function lpHasForum()
13024
    {
13025
        $forumTable = Database::get_course_table(TABLE_FORUM);
13026
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13027
13028
        $fakeFrom = "
13029
            $forumTable f
13030
            INNER JOIN $itemProperty ip
13031
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
13032
        ";
13033
13034
        $resultData = Database::select(
13035
            'COUNT(f.iid) AS qty',
13036
            $fakeFrom,
13037
            [
13038
                'where' => [
13039
                    'ip.visibility != ? AND ' => 2,
13040
                    'ip.tool = ? AND ' => TOOL_FORUM,
13041
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13042
                    'f.lp_id = ?' => intval($this->lp_id),
13043
                ],
13044
            ],
13045
            'first'
13046
        );
13047
13048
        return $resultData['qty'] > 0;
13049
    }
13050
13051
    /**
13052
     * Get the forum for this learning path.
13053
     *
13054
     * @param int $sessionId
13055
     *
13056
     * @return bool
13057
     */
13058
    public function getForum($sessionId = 0)
13059
    {
13060
        $forumTable = Database::get_course_table(TABLE_FORUM);
13061
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13062
13063
        $fakeFrom = "$forumTable f
13064
            INNER JOIN $itemProperty ip ";
13065
13066
        if ($this->lp_session_id == 0) {
13067
            $fakeFrom .= "
13068
                ON (
13069
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13070
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13071
                    )
13072
                )
13073
            ";
13074
        } else {
13075
            $fakeFrom .= "
13076
                ON (
13077
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13078
                )
13079
            ";
13080
        }
13081
13082
        $resultData = Database::select(
13083
            'f.*',
13084
            $fakeFrom,
13085
            [
13086
                'where' => [
13087
                    'ip.visibility != ? AND ' => 2,
13088
                    'ip.tool = ? AND ' => TOOL_FORUM,
13089
                    'f.session_id = ? AND ' => $sessionId,
13090
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13091
                    'f.lp_id = ?' => intval($this->lp_id),
13092
                ],
13093
            ],
13094
            'first'
13095
        );
13096
13097
        if (empty($resultData)) {
13098
            return false;
13099
        }
13100
13101
        return $resultData;
13102
    }
13103
13104
    /**
13105
     * Create a forum for this learning path.
13106
     *
13107
     * @param int $forumCategoryId
13108
     *
13109
     * @return int The forum ID if was created. Otherwise return false
13110
     */
13111
    public function createForum($forumCategoryId)
13112
    {
13113
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13114
13115
        $forumId = store_forum(
13116
            [
13117
                'lp_id' => $this->lp_id,
13118
                'forum_title' => $this->name,
13119
                'forum_comment' => null,
13120
                'forum_category' => (int) $forumCategoryId,
13121
                'students_can_edit_group' => ['students_can_edit' => 0],
13122
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13123
                'default_view_type_group' => ['default_view_type' => 'flat'],
13124
                'group_forum' => 0,
13125
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13126
            ],
13127
            [],
13128
            true
13129
        );
13130
13131
        return $forumId;
13132
    }
13133
13134
    /**
13135
     * Get the LP Final Item form.
13136
     *
13137
     * @throws Exception
13138
     * @throws HTML_QuickForm_Error
13139
     *
13140
     * @return string
13141
     */
13142
    public function getFinalItemForm()
13143
    {
13144
        $finalItem = $this->getFinalItem();
13145
        $title = '';
13146
13147
        if ($finalItem) {
13148
            $title = $finalItem->get_title();
13149
            $buttonText = get_lang('Save');
13150
            $content = $this->getSavedFinalItem();
13151
        } else {
13152
            $buttonText = get_lang('LPCreateDocument');
13153
            $content = $this->getFinalItemTemplate();
13154
        }
13155
13156
        $courseInfo = api_get_course_info();
13157
        $result = $this->generate_lp_folder($courseInfo);
13158
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13159
        $relative_prefix = '../../';
13160
13161
        $editorConfig = [
13162
            'ToolbarSet' => 'LearningPathDocuments',
13163
            'Width' => '100%',
13164
            'Height' => '500',
13165
            'FullPage' => true,
13166
            'CreateDocumentDir' => $relative_prefix,
13167
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13168
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13169
        ];
13170
13171
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13172
            'type' => 'document',
13173
            'lp_id' => $this->lp_id,
13174
        ]);
13175
13176
        $form = new FormValidator('final_item', 'POST', $url);
13177
        $form->addText('title', get_lang('Title'));
13178
        $form->addButtonSave($buttonText);
13179
        $form->addHtml(
13180
            Display::return_message(
13181
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13182
                'normal',
13183
                false
13184
            )
13185
        );
13186
13187
        $renderer = $form->defaultRenderer();
13188
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13189
13190
        $form->addHtmlEditor(
13191
            'content_lp_certificate',
13192
            null,
13193
            true,
13194
            false,
13195
            $editorConfig,
13196
            true
13197
        );
13198
        $form->addHidden('action', 'add_final_item');
13199
        $form->addHidden('path', Session::read('pathItem'));
13200
        $form->addHidden('previous', $this->get_last());
13201
        $form->setDefaults(
13202
            ['title' => $title, 'content_lp_certificate' => $content]
13203
        );
13204
13205
        if ($form->validate()) {
13206
            $values = $form->exportValues();
13207
            $lastItemId = $this->getLastInFirstLevel();
13208
13209
            if (!$finalItem) {
13210
                $documentId = $this->create_document(
13211
                    $this->course_info,
13212
                    $values['content_lp_certificate'],
13213
                    $values['title']
13214
                );
13215
                $this->add_item(
13216
                    0,
13217
                    $lastItemId,
13218
                    'final_item',
13219
                    $documentId,
13220
                    $values['title'],
13221
                    ''
13222
                );
13223
13224
                Display::addFlash(
13225
                    Display::return_message(get_lang('Added'))
13226
                );
13227
            } else {
13228
                $this->edit_document($this->course_info);
13229
            }
13230
        }
13231
13232
        return $form->returnForm();
13233
    }
13234
13235
    /**
13236
     * Check if the current lp item is first, both, last or none from lp list.
13237
     *
13238
     * @param int $currentItemId
13239
     *
13240
     * @return string
13241
     */
13242
    public function isFirstOrLastItem($currentItemId)
13243
    {
13244
        if ($this->debug > 0) {
13245
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
13246
        }
13247
13248
        $lpItemId = [];
13249
        $typeListNotToVerify = self::getChapterTypes();
13250
13251
        // Using get_toc() function instead $this->items because returns the correct order of the items
13252
        foreach ($this->get_toc() as $item) {
13253
            if (!in_array($item['type'], $typeListNotToVerify)) {
13254
                $lpItemId[] = $item['id'];
13255
            }
13256
        }
13257
13258
        $lastLpItemIndex = count($lpItemId) - 1;
13259
        $position = array_search($currentItemId, $lpItemId);
13260
13261
        switch ($position) {
13262
            case 0:
13263
                if (!$lastLpItemIndex) {
13264
                    $answer = 'both';
13265
                    break;
13266
                }
13267
13268
                $answer = 'first';
13269
                break;
13270
            case $lastLpItemIndex:
13271
                $answer = 'last';
13272
                break;
13273
            default:
13274
                $answer = 'none';
13275
        }
13276
13277
        return $answer;
13278
    }
13279
13280
    /**
13281
     * Get whether this is a learning path with the accumulated SCORM time or not.
13282
     *
13283
     * @return int
13284
     */
13285
    public function getAccumulateScormTime()
13286
    {
13287
        return $this->accumulateScormTime;
13288
    }
13289
13290
    /**
13291
     * Set whether this is a learning path with the accumulated SCORM time or not.
13292
     *
13293
     * @param int $value (0 = false, 1 = true)
13294
     *
13295
     * @return bool Always returns true
13296
     */
13297
    public function setAccumulateScormTime($value)
13298
    {
13299
        if ($this->debug > 0) {
13300
            error_log('In learnpath::setAccumulateScormTime()', 0);
13301
        }
13302
        $this->accumulateScormTime = (int) $value;
13303
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13304
        $lp_id = $this->get_id();
13305
        $sql = "UPDATE $lp_table 
13306
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13307
                WHERE iid = $lp_id";
13308
        Database::query($sql);
13309
13310
        return true;
13311
    }
13312
13313
    /**
13314
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13315
     * the new learning path tool.
13316
     *
13317
     * The function is a big switch on tool type.
13318
     * In each case, we query the corresponding table for information and build the link
13319
     * with that information.
13320
     *
13321
     * @author Yannick Warnier <[email protected]> - rebranding based on
13322
     * previous work (display_addedresource_link_in_learnpath())
13323
     *
13324
     * @param int $course_id      Course code
13325
     * @param int $learningPathId The learning path ID (in lp table)
13326
     * @param int $id_in_path     the unique index in the items table
13327
     * @param int $lpViewId
13328
     *
13329
     * @return string
13330
     */
13331
    public static function rl_get_resource_link_for_learnpath(
13332
        $course_id,
13333
        $learningPathId,
13334
        $id_in_path,
13335
        $lpViewId
13336
    ) {
13337
        $session_id = api_get_session_id();
13338
        $course_info = api_get_course_info_by_id($course_id);
13339
13340
        $learningPathId = (int) $learningPathId;
13341
        $id_in_path = (int) $id_in_path;
13342
        $lpViewId = (int) $lpViewId;
13343
13344
        $em = Database::getManager();
13345
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13346
13347
        /** @var CLpItem $rowItem */
13348
        $rowItem = $lpItemRepo->findOneBy([
13349
            'cId' => $course_id,
13350
            'lpId' => $learningPathId,
13351
            'iid' => $id_in_path,
13352
        ]);
13353
13354
        if (!$rowItem) {
13355
            // Try one more time with "id"
13356
            /** @var CLpItem $rowItem */
13357
            $rowItem = $lpItemRepo->findOneBy([
13358
                'cId' => $course_id,
13359
                'lpId' => $learningPathId,
13360
                'id' => $id_in_path,
13361
            ]);
13362
13363
            if (!$rowItem) {
13364
                return -1;
13365
            }
13366
        }
13367
13368
        $course_code = $course_info['code'];
13369
        $type = $rowItem->getItemType();
13370
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13371
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13372
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13373
        $link = '';
13374
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13375
13376
        switch ($type) {
13377
            case 'dir':
13378
                return $main_dir_path.'lp/blank.php';
13379
            case TOOL_CALENDAR_EVENT:
13380
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13381
            case TOOL_ANNOUNCEMENT:
13382
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13383
            case TOOL_LINK:
13384
                $linkInfo = Link::getLinkInfo($id);
13385
                if (isset($linkInfo['url'])) {
13386
                    return $linkInfo['url'];
13387
                }
13388
13389
                return '';
13390
            case TOOL_QUIZ:
13391
                if (empty($id)) {
13392
                    return '';
13393
                }
13394
13395
                // Get the lp_item_view with the highest view_count.
13396
                $learnpathItemViewResult = $em
13397
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13398
                    ->findBy(
13399
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13400
                        ['viewCount' => 'DESC'],
13401
                        1
13402
                    );
13403
                /** @var CLpItemView $learnpathItemViewData */
13404
                $learnpathItemViewData = current($learnpathItemViewResult);
13405
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13406
13407
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13408
                    .http_build_query([
13409
                        'lp_init' => 1,
13410
                        'learnpath_item_view_id' => $learnpathItemViewId,
13411
                        'learnpath_id' => $learningPathId,
13412
                        'learnpath_item_id' => $id_in_path,
13413
                        'exerciseId' => $id,
13414
                    ]);
13415
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13416
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13417
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13418
                $myrow = Database::fetch_array($result);
13419
                $path = $myrow['path'];
13420
13421
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13422
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13423
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13424
            case TOOL_FORUM:
13425
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13426
            case TOOL_THREAD:
13427
                // forum post
13428
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13429
                if (empty($id)) {
13430
                    return '';
13431
                }
13432
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13433
                $result = Database::query($sql);
13434
                $myrow = Database::fetch_array($result);
13435
13436
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13437
                    .$extraParams;
13438
            case TOOL_POST:
13439
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13440
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13441
                $myrow = Database::fetch_array($result);
13442
13443
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13444
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13445
            case TOOL_READOUT_TEXT:
13446
                return api_get_path(WEB_CODE_PATH).
13447
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13448
            case TOOL_DOCUMENT:
13449
                $document = $em
13450
                    ->getRepository('ChamiloCourseBundle:CDocument')
13451
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
13452
13453
                if (empty($document)) {
13454
                    // Try with normal id
13455
                    $document = $em
13456
                        ->getRepository('ChamiloCourseBundle:CDocument')
13457
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
13458
13459
                    if (empty($document)) {
13460
                        return '';
13461
                    }
13462
                }
13463
13464
                $documentPathInfo = pathinfo($document->getPath());
13465
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
13466
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13467
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13468
13469
                $openmethod = 2;
13470
                $officedoc = false;
13471
                Session::write('openmethod', $openmethod);
13472
                Session::write('officedoc', $officedoc);
13473
13474
                if ($showDirectUrl) {
13475
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13476
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13477
                        if (Link::isPdfLink($file)) {
13478
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13479
13480
                            return $pdfUrl;
13481
                        }
13482
                    }
13483
13484
                    return $file;
13485
                }
13486
13487
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13488
            case TOOL_LP_FINAL_ITEM:
13489
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13490
                    .$extraParams;
13491
            case 'assignments':
13492
                return $main_dir_path.'work/work.php?'.$extraParams;
13493
            case TOOL_DROPBOX:
13494
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13495
            case 'introduction_text': //DEPRECATED
13496
                return '';
13497
            case TOOL_COURSE_DESCRIPTION:
13498
                return $main_dir_path.'course_description?'.$extraParams;
13499
            case TOOL_GROUP:
13500
                return $main_dir_path.'group/group.php?'.$extraParams;
13501
            case TOOL_USER:
13502
                return $main_dir_path.'user/user.php?'.$extraParams;
13503
            case TOOL_STUDENTPUBLICATION:
13504
                if (!empty($rowItem->getPath())) {
13505
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13506
                }
13507
13508
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13509
        }
13510
13511
        return $link;
13512
    }
13513
13514
    /**
13515
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13516
     *
13517
     * @author Yannick Warnier <[email protected]>
13518
     *
13519
     * @param string $course_code    Course code
13520
     * @param int    $learningPathId
13521
     * @param int    $id_in_path     The resource ID
13522
     *
13523
     * @return string
13524
     */
13525
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13526
    {
13527
        $_course = api_get_course_info($course_code);
13528
        $course_id = $_course['real_id'];
13529
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13530
        $learningPathId = (int) $learningPathId;
13531
        $id_in_path = (int) $id_in_path;
13532
13533
        $sql = "SELECT item_type, title, ref 
13534
                FROM $tbl_lp_item
13535
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13536
        $res_item = Database::query($sql);
13537
13538
        if (Database::num_rows($res_item) < 1) {
13539
            return '';
13540
        }
13541
        $row_item = Database::fetch_array($res_item);
13542
        $type = strtolower($row_item['item_type']);
13543
        $id = $row_item['ref'];
13544
        $output = '';
13545
13546
        switch ($type) {
13547
            case TOOL_CALENDAR_EVENT:
13548
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13549
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13550
                $myrow = Database::fetch_array($result);
13551
                $output = $myrow['title'];
13552
                break;
13553
            case TOOL_ANNOUNCEMENT:
13554
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13555
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13556
                $myrow = Database::fetch_array($result);
13557
                $output = $myrow['title'];
13558
                break;
13559
            case TOOL_LINK:
13560
                // Doesn't take $target into account.
13561
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13562
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13563
                $myrow = Database::fetch_array($result);
13564
                $output = $myrow['title'];
13565
                break;
13566
            case TOOL_QUIZ:
13567
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13568
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13569
                $myrow = Database::fetch_array($result);
13570
                $output = $myrow['title'];
13571
                break;
13572
            case TOOL_FORUM:
13573
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13574
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13575
                $myrow = Database::fetch_array($result);
13576
                $output = $myrow['forum_name'];
13577
                break;
13578
            case TOOL_THREAD:  //=topics
13579
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13580
                // Grabbing the title of the post.
13581
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13582
                $result_title = Database::query($sql_title);
13583
                $myrow_title = Database::fetch_array($result_title);
13584
                $output = $myrow_title['post_title'];
13585
                break;
13586
            case TOOL_POST:
13587
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13588
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13589
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13590
                $result = Database::query($sql);
13591
                $post = Database::fetch_array($result);
13592
                $output = $post['post_title'];
13593
                break;
13594
            case 'dir':
13595
                $title = $row_item['title'];
13596
                if (!empty($title)) {
13597
                    $output = $title;
13598
                } else {
13599
                    $output = '-';
13600
                }
13601
                break;
13602
            case TOOL_DOCUMENT:
13603
                $title = $row_item['title'];
13604
                if (!empty($title)) {
13605
                    $output = $title;
13606
                } else {
13607
                    $output = '-';
13608
                }
13609
                break;
13610
            case 'hotpotatoes':
13611
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13612
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13613
                $myrow = Database::fetch_array($result);
13614
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13615
                $last = count($pathname) - 1; // Making a correct name for the link.
13616
                $filename = $pathname[$last]; // Making a correct name for the link.
13617
                $ext = explode('.', $filename);
13618
                $ext = strtolower($ext[sizeof($ext) - 1]);
13619
                $myrow['path'] = rawurlencode($myrow['path']);
13620
                $output = $filename;
13621
                break;
13622
        }
13623
13624
        return stripslashes($output);
13625
    }
13626
13627
    /**
13628
     * Get the parent names for the current item.
13629
     *
13630
     * @param int $newItemId Optional. The item ID
13631
     *
13632
     * @return array
13633
     */
13634
    public function getCurrentItemParentNames($newItemId = 0)
13635
    {
13636
        $newItemId = $newItemId ?: $this->get_current_item_id();
13637
        $return = [];
13638
        $item = $this->getItem($newItemId);
13639
        $parent = $this->getItem($item->get_parent());
13640
13641
        while ($parent) {
13642
            $return[] = $parent->get_title();
13643
13644
            $parent = $this->getItem($parent->get_parent());
13645
        }
13646
13647
        return array_reverse($return);
13648
    }
13649
13650
    /**
13651
     * Reads and process "lp_subscription_settings" setting.
13652
     *
13653
     * @return array
13654
     */
13655
    public static function getSubscriptionSettings()
13656
    {
13657
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13658
        if (empty($subscriptionSettings)) {
13659
            // By default allow both settings
13660
            $subscriptionSettings = [
13661
                'allow_add_users_to_lp' => true,
13662
                'allow_add_users_to_lp_category' => true,
13663
            ];
13664
        } else {
13665
            $subscriptionSettings = $subscriptionSettings['options'];
13666
        }
13667
13668
        return $subscriptionSettings;
13669
    }
13670
13671
    /**
13672
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13673
     */
13674
    public function exportToCourseBuildFormat()
13675
    {
13676
        if (!api_is_allowed_to_edit()) {
13677
            return false;
13678
        }
13679
13680
        $courseBuilder = new CourseBuilder();
13681
        $itemList = [];
13682
        /** @var learnpathItem $item */
13683
        foreach ($this->items as $item) {
13684
            $itemList[$item->get_type()][] = $item->get_path();
13685
        }
13686
13687
        if (empty($itemList)) {
13688
            return false;
13689
        }
13690
13691
        if (isset($itemList['document'])) {
13692
            // Get parents
13693
            foreach ($itemList['document'] as $documentId) {
13694
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13695
                if (!empty($documentInfo['parents'])) {
13696
                    foreach ($documentInfo['parents'] as $parentInfo) {
13697
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13698
                            continue;
13699
                        }
13700
                        $itemList['document'][] = $parentInfo['iid'];
13701
                    }
13702
                }
13703
            }
13704
13705
            $courseInfo = api_get_course_info();
13706
            foreach ($itemList['document'] as $documentId) {
13707
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13708
                $items = DocumentManager::get_resources_from_source_html(
13709
                    $documentInfo['absolute_path'],
13710
                    true,
13711
                    TOOL_DOCUMENT
13712
                );
13713
13714
                if (!empty($items)) {
13715
                    foreach ($items as $item) {
13716
                        // Get information about source url
13717
                        $url = $item[0]; // url
13718
                        $scope = $item[1]; // scope (local, remote)
13719
                        $type = $item[2]; // type (rel, abs, url)
13720
13721
                        $origParseUrl = parse_url($url);
13722
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13723
13724
                        if ($scope == 'local') {
13725
                            if ($type == 'abs' || $type == 'rel') {
13726
                                $documentFile = strstr($realOrigPath, 'document');
13727
                                if (strpos($realOrigPath, $documentFile) !== false) {
13728
                                    $documentFile = str_replace('document', '', $documentFile);
13729
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13730
                                    // Document found! Add it to the list
13731
                                    if ($itemDocumentId) {
13732
                                        $itemList['document'][] = $itemDocumentId;
13733
                                    }
13734
                                }
13735
                            }
13736
                        }
13737
                    }
13738
                }
13739
            }
13740
13741
            $courseBuilder->build_documents(
13742
                api_get_session_id(),
13743
                $this->get_course_int_id(),
13744
                true,
13745
                $itemList['document']
13746
            );
13747
        }
13748
13749
        if (isset($itemList['quiz'])) {
13750
            $courseBuilder->build_quizzes(
13751
                api_get_session_id(),
13752
                $this->get_course_int_id(),
13753
                true,
13754
                $itemList['quiz']
13755
            );
13756
        }
13757
13758
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13759
13760
        /*if (!empty($itemList['thread'])) {
13761
            $postList = [];
13762
            foreach ($itemList['thread'] as $postId) {
13763
                $post = get_post_information($postId);
13764
                if ($post) {
13765
                    if (!isset($itemList['forum'])) {
13766
                        $itemList['forum'] = [];
13767
                    }
13768
                    $itemList['forum'][] = $post['forum_id'];
13769
                    $postList[] = $postId;
13770
                }
13771
            }
13772
13773
            if (!empty($postList)) {
13774
                $courseBuilder->build_forum_posts(
13775
                    $this->get_course_int_id(),
13776
                    null,
13777
                    null,
13778
                    $postList
13779
                );
13780
            }
13781
        }*/
13782
13783
        if (!empty($itemList['thread'])) {
13784
            $threadList = [];
13785
            $em = Database::getManager();
13786
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13787
            foreach ($itemList['thread'] as $threadId) {
13788
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13789
                $thread = $repo->find($threadId);
13790
                if ($thread) {
13791
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13792
                    $threadList[] = $thread->getIid();
13793
                }
13794
            }
13795
13796
            if (!empty($threadList)) {
13797
                $courseBuilder->build_forum_topics(
13798
                    api_get_session_id(),
13799
                    $this->get_course_int_id(),
13800
                    null,
13801
                    $threadList
13802
                );
13803
            }
13804
        }
13805
13806
        $forumCategoryList = [];
13807
        if (isset($itemList['forum'])) {
13808
            foreach ($itemList['forum'] as $forumId) {
13809
                $forumInfo = get_forums($forumId);
13810
                $forumCategoryList[] = $forumInfo['forum_category'];
13811
            }
13812
        }
13813
13814
        if (!empty($forumCategoryList)) {
13815
            $courseBuilder->build_forum_category(
13816
                api_get_session_id(),
13817
                $this->get_course_int_id(),
13818
                true,
13819
                $forumCategoryList
13820
            );
13821
        }
13822
13823
        if (!empty($itemList['forum'])) {
13824
            $courseBuilder->build_forums(
13825
                api_get_session_id(),
13826
                $this->get_course_int_id(),
13827
                true,
13828
                $itemList['forum']
13829
            );
13830
        }
13831
13832
        if (isset($itemList['link'])) {
13833
            $courseBuilder->build_links(
13834
                api_get_session_id(),
13835
                $this->get_course_int_id(),
13836
                true,
13837
                $itemList['link']
13838
            );
13839
        }
13840
13841
        if (!empty($itemList['student_publication'])) {
13842
            $courseBuilder->build_works(
13843
                api_get_session_id(),
13844
                $this->get_course_int_id(),
13845
                true,
13846
                $itemList['student_publication']
13847
            );
13848
        }
13849
13850
        $courseBuilder->build_learnpaths(
13851
            api_get_session_id(),
13852
            $this->get_course_int_id(),
13853
            true,
13854
            [$this->get_id()],
13855
            false
13856
        );
13857
13858
        $courseBuilder->restoreDocumentsFromList();
13859
13860
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13861
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13862
        $result = DocumentManager::file_send_for_download(
13863
            $zipPath,
13864
            true,
13865
            $this->get_name().'.zip'
13866
        );
13867
13868
        if ($result) {
13869
            api_not_allowed();
13870
        }
13871
13872
        return true;
13873
    }
13874
13875
    /**
13876
     * Get whether this is a learning path with the accumulated work time or not.
13877
     *
13878
     * @return int
13879
     */
13880
    public function getAccumulateWorkTime()
13881
    {
13882
        return (int) $this->accumulateWorkTime;
13883
    }
13884
13885
    /**
13886
     * Get whether this is a learning path with the accumulated work time or not.
13887
     *
13888
     * @return int
13889
     */
13890
    public function getAccumulateWorkTimeTotalCourse()
13891
    {
13892
        $table = Database::get_course_table(TABLE_LP_MAIN);
13893
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13894
                FROM $table 
13895
                WHERE c_id = ".$this->course_int_id;
13896
        $result = Database::query($sql);
13897
        $row = Database::fetch_array($result);
13898
13899
        return (int) $row['total'];
13900
    }
13901
13902
    /**
13903
     * Set whether this is a learning path with the accumulated work time or not.
13904
     *
13905
     * @param int $value (0 = false, 1 = true)
13906
     *
13907
     * @return bool
13908
     */
13909
    public function setAccumulateWorkTime($value)
13910
    {
13911
        if (!api_get_configuration_value('lp_minimum_time')) {
13912
            return false;
13913
        }
13914
13915
        $this->accumulateWorkTime = (int) $value;
13916
        $table = Database::get_course_table(TABLE_LP_MAIN);
13917
        $lp_id = $this->get_id();
13918
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13919
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13920
        Database::query($sql);
13921
13922
        return true;
13923
    }
13924
13925
    /**
13926
     * @param int $lpId
13927
     * @param int $courseId
13928
     *
13929
     * @return mixed
13930
     */
13931
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13932
    {
13933
        $lpId = (int) $lpId;
13934
        $courseId = (int) $courseId;
13935
13936
        $table = Database::get_course_table(TABLE_LP_MAIN);
13937
        $sql = "SELECT accumulate_work_time 
13938
                FROM $table 
13939
                WHERE c_id = $courseId AND id = $lpId";
13940
        $result = Database::query($sql);
13941
        $row = Database::fetch_array($result);
13942
13943
        return $row['accumulate_work_time'];
13944
    }
13945
13946
    /**
13947
     * @param int $courseId
13948
     *
13949
     * @return int
13950
     */
13951
    public static function getAccumulateWorkTimeTotal($courseId)
13952
    {
13953
        $table = Database::get_course_table(TABLE_LP_MAIN);
13954
        $courseId = (int) $courseId;
13955
        $sql = "SELECT SUM(accumulate_work_time) AS total
13956
                FROM $table
13957
                WHERE c_id = $courseId";
13958
        $result = Database::query($sql);
13959
        $row = Database::fetch_array($result);
13960
13961
        return (int) $row['total'];
13962
    }
13963
13964
    /**
13965
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13966
     * and put the images in.
13967
     *
13968
     * @return array
13969
     */
13970
    public static function getIconSelect()
13971
    {
13972
        $theme = api_get_visual_theme();
13973
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13974
        $icons = ['' => get_lang('SelectAnOption')];
13975
13976
        if (is_dir($path)) {
13977
            $finder = new Finder();
13978
            $finder->files()->in($path);
13979
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13980
            /** @var SplFileInfo $file */
13981
            foreach ($finder as $file) {
13982
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13983
                    $icons[$file->getFilename()] = $file->getFilename();
13984
                }
13985
            }
13986
        }
13987
13988
        return $icons;
13989
    }
13990
13991
    /**
13992
     * @param int $lpId
13993
     *
13994
     * @return string
13995
     */
13996
    public static function getSelectedIcon($lpId)
13997
    {
13998
        $extraFieldValue = new ExtraFieldValue('lp');
13999
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
14000
        $icon = '';
14001
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
14002
            $icon = $lpIcon['value'];
14003
        }
14004
14005
        return $icon;
14006
    }
14007
14008
    /**
14009
     * @param int $lpId
14010
     *
14011
     * @return string
14012
     */
14013
    public static function getSelectedIconHtml($lpId)
14014
    {
14015
        $icon = self::getSelectedIcon($lpId);
14016
14017
        if (empty($icon)) {
14018
            return '';
14019
        }
14020
14021
        $theme = api_get_visual_theme();
14022
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14023
14024
        return Display::img($path);
14025
    }
14026
14027
    /**
14028
     * Get the depth level of LP item.
14029
     *
14030
     * @param array $items
14031
     * @param int   $currentItemId
14032
     *
14033
     * @return int
14034
     */
14035
    private static function get_level_for_item($items, $currentItemId)
14036
    {
14037
        $parentItemId = 0;
14038
        if (isset($items[$currentItemId])) {
14039
            $parentItemId = $items[$currentItemId]->parent;
14040
        }
14041
14042
        if ($parentItemId == 0) {
14043
            return 0;
14044
        } else {
14045
            return self::get_level_for_item($items, $parentItemId) + 1;
14046
        }
14047
    }
14048
14049
    /**
14050
     * Generate the link for a learnpath category as course tool.
14051
     *
14052
     * @param int $categoryId
14053
     *
14054
     * @return string
14055
     */
14056
    private static function getCategoryLinkForTool($categoryId)
14057
    {
14058
        $categoryId = (int) $categoryId;
14059
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14060
            .http_build_query(
14061
                [
14062
                    'action' => 'view_category',
14063
                    'id' => $categoryId,
14064
                ]
14065
            );
14066
14067
        return $link;
14068
    }
14069
14070
    /**
14071
     * Return the scorm item type object with spaces replaced with _
14072
     * The return result is use to build a css classname like scorm_type_$return.
14073
     *
14074
     * @param $in_type
14075
     *
14076
     * @return mixed
14077
     */
14078
    private static function format_scorm_type_item($in_type)
14079
    {
14080
        return str_replace(' ', '_', $in_type);
14081
    }
14082
14083
    /**
14084
     * Check and obtain the lp final item if exist.
14085
     *
14086
     * @return learnpathItem
14087
     */
14088
    private function getFinalItem()
14089
    {
14090
        if (empty($this->items)) {
14091
            return null;
14092
        }
14093
14094
        foreach ($this->items as $item) {
14095
            if ($item->type !== 'final_item') {
14096
                continue;
14097
            }
14098
14099
            return $item;
14100
        }
14101
    }
14102
14103
    /**
14104
     * Get the LP Final Item Template.
14105
     *
14106
     * @return string
14107
     */
14108
    private function getFinalItemTemplate()
14109
    {
14110
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14111
    }
14112
14113
    /**
14114
     * Get the LP Final Item Url.
14115
     *
14116
     * @return string
14117
     */
14118
    private function getSavedFinalItem()
14119
    {
14120
        $finalItem = $this->getFinalItem();
14121
        $doc = DocumentManager::get_document_data_by_id(
14122
            $finalItem->path,
14123
            $this->cc
14124
        );
14125
        if ($doc && file_exists($doc['absolute_path'])) {
14126
            return file_get_contents($doc['absolute_path']);
14127
        }
14128
14129
        return '';
14130
    }
14131
}
14132