Passed
Push — master ( e739b7...1cdc43 )
by Julito
10:11
created

learnpath   F

Complexity

Total Complexity 1945

Size/Duplication

Total Lines 14097
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7906
dl 0
loc 14097
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
F display_document_form() 0 375 73
F displayFrmReadOutText() 0 304 60
A getCurrentBuildingModeURL() 0 10 5
F display_thread_form() 0 216 42
A display_document() 0 20 2
F display_quiz_form() 0 192 36
C display_item() 0 98 16
F display_hotpotatoes_form() 0 180 37
A display_resources() 0 49 1
F edit_document() 0 82 17
F display_forum_form() 0 205 40
A get_extension() 0 5 1
F __construct() 0 337 55
A getProgressBar() 0 5 1
B update_default_view_mode() 0 40 8
A next() 0 22 5
A get_iv_objectives_array() 0 34 3
A getCategory() 0 7 1
D display_edit_item() 0 121 21
A set_autolaunch() 0 27 2
A get_total_items_count() 0 7 2
B getCalculateScore() 0 47 6
B save_last() 0 48 10
A get_first_item_id() 0 8 2
A get_update_queue() 0 7 2
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
D prerequisites_match() 0 67 16
A previous() 0 14 2
A tree_array() 0 7 2
A createCategory() 0 18 1
B move_down() 0 54 8
B get_exercises() 0 105 6
C fixBlockedLinks() 0 64 11
A open() 0 12 2
F edit_item() 0 213 24
A set_jslib() 0 16 3
A createForum() 0 21 1
B update_default_scorm_commit() 0 32 6
C getChildrenToc() 0 61 12
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 get_author() 0 9 3
A display_lp_prerequisites_list() 0 31 5
A set_prerequisite() 0 16 3
A set_modified_on() 0 16 3
A get_last() 0 13 3
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 26 2
A get_type() 0 14 6
A get_maker() 0 9 3
F display_link_form() 0 191 34
A getForum() 0 44 3
A get_progress_bar() 0 12 1
A set_preview_image() 0 18 3
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
A getSelectedIcon() 0 10 3
B getSiblingDirectories() 0 35 6
B get_brother_items() 0 31 6
C toggle_publish() 0 80 11
A getCountCategories() 0 10 2
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A get_type_static() 0 16 3
A sort_tree_array() 0 12 3
F scormExport() 0 974 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A get_items_status_list() 0 13 3
A get_theme() 0 9 3
F display_item_form() 0 232 39
A select_previous_item_id() 0 25 2
F add_item() 0 220 17
B set_seriousgame_mode() 0 30 6
A get_preview_image() 0 9 3
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
D get_progress_bar_text() 0 43 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
F first() 0 71 20
A getItem() 0 7 3
A getCategoryId() 0 3 1
C create_tree_array() 0 41 12
B get_links() 0 109 6
A get_items_details_as_js() 0 14 4
A get_progress_bar_mode() 0 9 3
B set_terms_by_prefix() 0 68 10
A get_user_id() 0 9 3
A set_use_max_score() 0 19 3
A toggle_visibility() 0 14 2
A create_path() 0 14 5
A get_current_item_id() 0 14 4
A set_previous_item() 0 6 2
B upload_image() 0 40 6
A setSubscribeUsers() 0 13 2
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A set_theme() 0 17 3
A getChapterTypes() 0 4 1
C rl_get_resource_name() 0 100 14
A getSavedFinalItem() 0 12 3
B get_preview_image_path() 0 28 7
B restart() 0 40 6
A set_author() 0 16 3
A get_iv_interactions_array() 0 42 4
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 close() 0 16 3
A deleteCategory() 0 37 4
A update_display_order() 0 29 5
B generate_lp_folder() 0 58 6
A getAccumulateWorkTimeTotalCourse() 0 10 1
A moveUpCategory() 0 11 2
C get_mediaplayer() 0 87 13
A set_proximity() 0 21 4
B overview() 0 54 10
A set_expired_on() 0 31 5
F autocomplete_parents() 0 105 18
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
F display_item_prerequisites_form() 0 188 19
B print_recursive() 0 40 10
A clear_prerequisites() 0 17 2
C scorm_export_to_pdf() 0 63 12
B isFirstOrLastItem() 0 36 7
B display_move_item() 0 55 11
C getCalculateStars() 0 80 12
B get_scorm_xml_node() 0 19 7
A has_audio() 0 14 4
A set_encoding() 0 23 5
B edit_item_prereq() 0 45 8
B get_js_info() 0 44 6
A get_id() 0 6 2
B get_js_dropdown_array() 0 74 6
A getFinalEvaluationItem() 0 12 3
B build_action_menu() 0 126 5
B get_view() 0 41 6
A getCategoryByCourse() 0 8 1
F is_lp_visible_for_student() 0 129 23
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A getCategoryLinkForTool() 0 12 1
A getSubscriptionSettings() 0 14 2
B verify_document_size() 0 21 8
F display_manipulate() 0 121 16
A get_complete_items_count() 0 27 6
A get_objectives_count_from_db() 0 16 2
B get_documents() 0 99 2
B delete_item() 0 78 9
C get_forums() 0 111 11
F move_item() 0 153 27
A getTotalItemsCountWithoutDirs() 0 14 4
A delete_lp_image() 0 17 5
B update_scorm_debug() 0 30 6
A toggleCategoryVisibility() 0 29 3
C getParentToc() 0 67 14
F add_lp() 0 147 14
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 14 2
B update_reinit() 0 30 6
A getFinalItemTemplate() 0 3 1
F getListArrayToc() 0 79 12
F rl_get_resource_link_for_learnpath() 0 181 34
A get_view_id() 0 9 3
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
B toggleCategoryPublish() 0 88 9
D get_navigation_bar() 0 76 11
A generate_learning_path_folder() 0 25 3
A set_hide_toc_frame() 0 20 4
A getCategories() 0 17 1
B get_attempt_mode() 0 21 9
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 9 3
A return_new_tree() 0 35 4
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 31
A get_toc() 0 27 5
C categoryIsVisibleForStudent() 0 73 15
A get_level_for_item() 0 11 3
F delete() 0 111 18
A set_maker() 0 20 4
A get_student_publications() 0 41 3
A get_name() 0 9 3
A lpHasForum() 0 26 1
F display_student_publication_form() 0 175 30
A setAccumulateWorkTime() 0 14 2
A delete_children_items() 0 23 5
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)
3013
    {
3014
        $course_id = api_get_course_int_id();
3015
        $list = [];
3016
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
3017
3018
        if (empty($lp_iv_id)) {
3019
            return [];
3020
        }
3021
3022
        $sql = "SELECT * FROM $table
3023
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
3024
                ORDER BY order_id ASC";
3025
        $res = Database::query($sql);
3026
        $num = Database::num_rows($res);
3027
        if ($num > 0) {
3028
            $list[] = [
3029
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3030
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
3031
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
3032
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
3033
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
3034
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
3035
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
3036
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
3037
            ];
3038
            while ($row = Database::fetch_array($res)) {
3039
                $list[] = [
3040
                    'order_id' => ($row['order_id'] + 1),
3041
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
3042
                    'type' => $row['interaction_type'],
3043
                    'time' => $row['completion_time'],
3044
                    //'correct_responses' => $row['correct_responses'],
3045
                    'correct_responses' => '', // Hide correct responses from students.
3046
                    'student_response' => $row['student_response'],
3047
                    'result' => $row['result'],
3048
                    'latency' => $row['latency'],
3049
                ];
3050
            }
3051
        }
3052
3053
        return $list;
3054
    }
3055
3056
    /**
3057
     * Return the number of objectives for the given learnpath Item View ID.
3058
     * This method can be used as static.
3059
     *
3060
     * @param int $lp_iv_id  Item View ID
3061
     * @param int $course_id Course ID
3062
     *
3063
     * @return int Number of objectives
3064
     */
3065
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
3066
    {
3067
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3068
        $course_id = (int) $course_id;
3069
        $lp_iv_id = (int) $lp_iv_id;
3070
        $sql = "SELECT count(*) FROM $table
3071
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
3072
        //@todo seems that this always returns 0
3073
        $res = Database::query($sql);
3074
        $num = 0;
3075
        if (Database::num_rows($res)) {
3076
            $row = Database::fetch_array($res);
3077
            $num = $row[0];
3078
        }
3079
3080
        return $num;
3081
    }
3082
3083
    /**
3084
     * Return the objectives as an array for the given lp_iv_id.
3085
     * This method can be used as static.
3086
     *
3087
     * @param int $lpItemViewId Learnpath Item View ID
3088
     *
3089
     * @return array
3090
     *
3091
     * @todo    Translate labels
3092
     */
3093
    public static function get_iv_objectives_array($lpItemViewId = 0)
3094
    {
3095
        $course_id = api_get_course_int_id();
3096
        $lpItemViewId = (int) $lpItemViewId;
3097
3098
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3099
        $sql = "SELECT * FROM $table
3100
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3101
                ORDER BY order_id ASC";
3102
        $res = Database::query($sql);
3103
        $num = Database::num_rows($res);
3104
        $list = [];
3105
        if ($num > 0) {
3106
            $list[] = [
3107
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3108
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3109
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3110
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3111
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3112
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3113
            ];
3114
            while ($row = Database::fetch_array($res)) {
3115
                $list[] = [
3116
                    'order_id' => ($row['order_id'] + 1),
3117
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3118
                    'score_raw' => $row['score_raw'],
3119
                    'score_max' => $row['score_max'],
3120
                    'score_min' => $row['score_min'],
3121
                    'status' => $row['status'],
3122
                ];
3123
            }
3124
        }
3125
3126
        return $list;
3127
    }
3128
3129
    /**
3130
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3131
     * used by get_html_toc() to be ready to display.
3132
     *
3133
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3134
     */
3135
    public function get_toc()
3136
    {
3137
        if ($this->debug > 0) {
3138
            error_log('learnpath::get_toc()', 0);
3139
        }
3140
        $toc = [];
3141
        foreach ($this->ordered_items as $item_id) {
3142
            if ($this->debug > 2) {
3143
                error_log('learnpath::get_toc(): getting info for item '.$item_id, 0);
3144
            }
3145
            // TODO: Change this link generation and use new function instead.
3146
            $toc[] = [
3147
                'id' => $item_id,
3148
                'title' => $this->items[$item_id]->get_title(),
3149
                'status' => $this->items[$item_id]->get_status(),
3150
                'level' => $this->items[$item_id]->get_level(),
3151
                'type' => $this->items[$item_id]->get_type(),
3152
                'description' => $this->items[$item_id]->get_description(),
3153
                'path' => $this->items[$item_id]->get_path(),
3154
                'parent' => $this->items[$item_id]->get_parent(),
3155
            ];
3156
        }
3157
        if ($this->debug > 2) {
3158
            error_log('In learnpath::get_toc() - TOC array: '.print_r($toc, true), 0);
3159
        }
3160
3161
        return $toc;
3162
    }
3163
3164
    /**
3165
     * Generate and return the table of contents for this learnpath. The JS
3166
     * table returned is used inside of scorm_api.php.
3167
     *
3168
     * @param string $varname
3169
     *
3170
     * @return string A JS array variable construction
3171
     */
3172
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3173
    {
3174
        if ($this->debug > 0) {
3175
            error_log('In learnpath::get_items_details_as_js()', 0);
3176
        }
3177
        $toc = $varname.' = new Array();';
3178
        foreach ($this->ordered_items as $item_id) {
3179
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3180
        }
3181
        if ($this->debug > 2) {
3182
            error_log('In learnpath::get_items_details_as_js() - TOC array: '.print_r($toc, true), 0);
3183
        }
3184
3185
        return $toc;
3186
    }
3187
3188
    /**
3189
     * Gets the learning path type.
3190
     *
3191
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3192
     *
3193
     * @return mixed Type ID or name, depending on the parameter
3194
     */
3195
    public function get_type($get_name = false)
3196
    {
3197
        $res = false;
3198
        if ($this->debug > 0) {
3199
            error_log('In learnpath::get_type()', 0);
3200
        }
3201
        if (!empty($this->type) && (!$get_name)) {
3202
            $res = $this->type;
3203
        }
3204
        if ($this->debug > 2) {
3205
            error_log('In learnpath::get_type() - Returning '.($res ? $res : 'false'), 0);
3206
        }
3207
3208
        return $res;
3209
    }
3210
3211
    /**
3212
     * Gets the learning path type as static method.
3213
     *
3214
     * @param int $lp_id
3215
     *
3216
     * @return mixed Type ID or name, depending on the parameter
3217
     */
3218
    public static function get_type_static($lp_id = 0)
3219
    {
3220
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3221
        $lp_id = (int) $lp_id;
3222
        $sql = "SELECT lp_type FROM $tbl_lp
3223
                WHERE iid = $lp_id";
3224
        $res = Database::query($sql);
3225
        if ($res === false) {
3226
            return null;
3227
        }
3228
        if (Database::num_rows($res) <= 0) {
3229
            return null;
3230
        }
3231
        $row = Database::fetch_array($res);
3232
3233
        return $row['lp_type'];
3234
    }
3235
3236
    /**
3237
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3238
     * This method can be used as abstract and is recursive.
3239
     *
3240
     * @param int $lp        Learnpath ID
3241
     * @param int $parent    Parent ID of the items to look for
3242
     * @param int $course_id
3243
     *
3244
     * @return array Ordered list of item IDs (empty array on error)
3245
     */
3246
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3247
    {
3248
        if (empty($course_id)) {
3249
            $course_id = api_get_course_int_id();
3250
        } else {
3251
            $course_id = (int) $course_id;
3252
        }
3253
        $list = [];
3254
3255
        if (empty($lp)) {
3256
            return $list;
3257
        }
3258
3259
        $lp = (int) $lp;
3260
        $parent = (int) $parent;
3261
3262
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3263
        $sql = "SELECT iid FROM $tbl_lp_item
3264
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3265
                ORDER BY display_order";
3266
3267
        $res = Database::query($sql);
3268
        while ($row = Database::fetch_array($res)) {
3269
            $sublist = self::get_flat_ordered_items_list(
3270
                $lp,
3271
                $row['iid'],
3272
                $course_id
3273
            );
3274
            $list[] = $row['iid'];
3275
            foreach ($sublist as $item) {
3276
                $list[] = $item;
3277
            }
3278
        }
3279
3280
        return $list;
3281
    }
3282
3283
    /**
3284
     * @return array
3285
     */
3286
    public static function getChapterTypes()
3287
    {
3288
        return [
3289
            'dir',
3290
        ];
3291
    }
3292
3293
    /**
3294
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3295
     *
3296
     * @param $tree
3297
     *
3298
     * @return array HTML TOC ready to display
3299
     */
3300
    public function getParentToc($tree)
3301
    {
3302
        if ($this->debug > 0) {
3303
            error_log('In learnpath::get_html_toc()', 0);
3304
        }
3305
        if (empty($tree)) {
3306
            $tree = $this->get_toc();
3307
        }
3308
        $dirTypes = self::getChapterTypes();
3309
        $myCurrentId = $this->get_current_item_id();
3310
        $listParent = [];
3311
        $listChildren = [];
3312
        $listNotParent = [];
3313
        $list = [];
3314
        foreach ($tree as $subtree) {
3315
            if (in_array($subtree['type'], $dirTypes)) {
3316
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3317
                $subtree['children'] = $listChildren;
3318
                if (!empty($subtree['children'])) {
3319
                    foreach ($subtree['children'] as $subItem) {
3320
                        if ($subItem['id'] == $this->current) {
3321
                            $subtree['parent_current'] = 'in';
3322
                            $subtree['current'] = 'on';
3323
                        }
3324
                    }
3325
                }
3326
                $listParent[] = $subtree;
3327
            }
3328
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3329
                $classStatus = [
3330
                    'not attempted' => 'scorm_not_attempted',
3331
                    'incomplete' => 'scorm_not_attempted',
3332
                    'failed' => 'scorm_failed',
3333
                    'completed' => 'scorm_completed',
3334
                    'passed' => 'scorm_completed',
3335
                    'succeeded' => 'scorm_completed',
3336
                    'browsed' => 'scorm_completed',
3337
                ];
3338
3339
                if (isset($classStatus[$subtree['status']])) {
3340
                    $cssStatus = $classStatus[$subtree['status']];
3341
                }
3342
3343
                $title = Security::remove_XSS($subtree['title']);
3344
                unset($subtree['title']);
3345
3346
                if (empty($title)) {
3347
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3348
                }
3349
                $classStyle = null;
3350
                if ($subtree['id'] == $this->current) {
3351
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3352
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3353
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3354
                }
3355
                $subtree['title'] = $title;
3356
                $subtree['class'] = $classStyle.' '.$cssStatus;
3357
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3358
                $subtree['current_id'] = $myCurrentId;
3359
                $listNotParent[] = $subtree;
3360
            }
3361
        }
3362
3363
        $list['are_parents'] = $listParent;
3364
        $list['not_parents'] = $listNotParent;
3365
3366
        return $list;
3367
    }
3368
3369
    /**
3370
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3371
     *
3372
     * @param array $tree
3373
     * @param int   $id
3374
     * @param bool  $parent
3375
     *
3376
     * @return array HTML TOC ready to display
3377
     */
3378
    public function getChildrenToc($tree, $id, $parent = true)
3379
    {
3380
        if ($this->debug > 0) {
3381
            error_log('In learnpath::get_html_toc()', 0);
3382
        }
3383
        if (empty($tree)) {
3384
            $tree = $this->get_toc();
3385
        }
3386
3387
        $dirTypes = self::getChapterTypes();
3388
        $mycurrentitemid = $this->get_current_item_id();
3389
        $list = [];
3390
        $classStatus = [
3391
            'not attempted' => 'scorm_not_attempted',
3392
            'incomplete' => 'scorm_not_attempted',
3393
            'failed' => 'scorm_failed',
3394
            'completed' => 'scorm_completed',
3395
            'passed' => 'scorm_completed',
3396
            'succeeded' => 'scorm_completed',
3397
            'browsed' => 'scorm_completed',
3398
        ];
3399
3400
        foreach ($tree as $subtree) {
3401
            $subtree['tree'] = null;
3402
3403
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3404
                if ($subtree['id'] == $this->current) {
3405
                    $subtree['current'] = 'active';
3406
                } else {
3407
                    $subtree['current'] = null;
3408
                }
3409
                if (isset($classStatus[$subtree['status']])) {
3410
                    $cssStatus = $classStatus[$subtree['status']];
3411
                }
3412
3413
                $title = Security::remove_XSS($subtree['title']);
3414
                unset($subtree['title']);
3415
                if (empty($title)) {
3416
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3417
                }
3418
3419
                $classStyle = null;
3420
                if ($subtree['id'] == $this->current) {
3421
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3422
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3423
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3424
                }
3425
3426
                if (in_array($subtree['type'], $dirTypes)) {
3427
                    $subtree['title'] = stripslashes($title);
3428
                } else {
3429
                    $subtree['title'] = $title;
3430
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3431
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3432
                    $subtree['current_id'] = $mycurrentitemid;
3433
                }
3434
                $list[] = $subtree;
3435
            }
3436
        }
3437
3438
        return $list;
3439
    }
3440
3441
    /**
3442
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3443
     *
3444
     * @param array $toc_list
3445
     *
3446
     * @return array HTML TOC ready to display
3447
     */
3448
    public function getListArrayToc($toc_list = [])
3449
    {
3450
        if ($this->debug > 0) {
3451
            error_log('In learnpath::get_html_toc()', 0);
3452
        }
3453
        if (empty($toc_list)) {
3454
            $toc_list = $this->get_toc();
3455
        }
3456
        // Temporary variables.
3457
        $mycurrentitemid = $this->get_current_item_id();
3458
        $list = [];
3459
        $arrayList = [];
3460
        $classStatus = [
3461
            'not attempted' => 'scorm_not_attempted',
3462
            'incomplete' => 'scorm_not_attempted',
3463
            'failed' => 'scorm_failed',
3464
            'completed' => 'scorm_completed',
3465
            'passed' => 'scorm_completed',
3466
            'succeeded' => 'scorm_completed',
3467
            'browsed' => 'scorm_completed',
3468
        ];
3469
3470
        foreach ($toc_list as $item) {
3471
            $list['id'] = $item['id'];
3472
            $list['status'] = $item['status'];
3473
            $cssStatus = null;
3474
3475
            if (isset($classStatus[$item['status']])) {
3476
                $cssStatus = $classStatus[$item['status']];
3477
            }
3478
3479
            $classStyle = ' ';
3480
            $dirTypes = self::getChapterTypes();
3481
3482
            if (in_array($item['type'], $dirTypes)) {
3483
                $classStyle = 'scorm_item_section ';
3484
            }
3485
            if ($item['id'] == $this->current) {
3486
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3487
            } elseif (!in_array($item['type'], $dirTypes)) {
3488
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3489
            }
3490
            $title = $item['title'];
3491
            if (empty($title)) {
3492
                $title = self::rl_get_resource_name(
3493
                    api_get_course_id(),
3494
                    $this->get_id(),
3495
                    $item['id']
3496
                );
3497
            }
3498
            $title = Security::remove_XSS($item['title']);
3499
3500
            if (empty($item['description'])) {
3501
                $list['description'] = $title;
3502
            } else {
3503
                $list['description'] = $item['description'];
3504
            }
3505
3506
            $list['class'] = $classStyle.' '.$cssStatus;
3507
            $list['level'] = $item['level'];
3508
            $list['type'] = $item['type'];
3509
3510
            if (in_array($item['type'], $dirTypes)) {
3511
                $list['css_level'] = 'level_'.$item['level'];
3512
            } else {
3513
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3514
            }
3515
3516
            if (in_array($item['type'], $dirTypes)) {
3517
                $list['title'] = stripslashes($title);
3518
            } else {
3519
                $list['title'] = stripslashes($title);
3520
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3521
                $list['current_id'] = $mycurrentitemid;
3522
            }
3523
            $arrayList[] = $list;
3524
        }
3525
3526
        return $arrayList;
3527
    }
3528
3529
    /**
3530
     * Returns an HTML-formatted string ready to display with teacher buttons
3531
     * in LP view menu.
3532
     *
3533
     * @return string HTML TOC ready to display
3534
     */
3535
    public function get_teacher_toc_buttons()
3536
    {
3537
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3538
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3539
        $html = '';
3540
        if ($isAllow && $hideIcons == false) {
3541
            if ($this->get_lp_session_id() == api_get_session_id()) {
3542
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3543
                $html .= '<div class="btn-group">';
3544
                $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'>".
3545
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3546
                $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'>".
3547
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3548
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3549
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3550
                $html .= '</div>';
3551
                $html .= '</div>';
3552
            }
3553
        }
3554
3555
        return $html;
3556
    }
3557
3558
    /**
3559
     * Gets the learnpath maker name - generally the editor's name.
3560
     *
3561
     * @return string Learnpath maker name
3562
     */
3563
    public function get_maker()
3564
    {
3565
        if ($this->debug > 0) {
3566
            error_log('In learnpath::get_maker()', 0);
3567
        }
3568
        if (!empty($this->maker)) {
3569
            return $this->maker;
3570
        } else {
3571
            return '';
3572
        }
3573
    }
3574
3575
    /**
3576
     * Gets the learnpath name/title.
3577
     *
3578
     * @return string Learnpath name/title
3579
     */
3580
    public function get_name()
3581
    {
3582
        if ($this->debug > 0) {
3583
            error_log('In learnpath::get_name()', 0);
3584
        }
3585
        if (!empty($this->name)) {
3586
            return $this->name;
3587
        } else {
3588
            return 'N/A';
3589
        }
3590
    }
3591
3592
    /**
3593
     * Gets a link to the resource from the present location, depending on item ID.
3594
     *
3595
     * @param string $type         Type of link expected
3596
     * @param int    $item_id      Learnpath item ID
3597
     * @param bool   $provided_toc
3598
     *
3599
     * @return string $provided_toc Link to the lp_item resource
3600
     */
3601
    public function get_link($type = 'http', $item_id = null, $provided_toc = false)
3602
    {
3603
        $course_id = $this->get_course_int_id();
3604
        if ($this->debug > 0) {
3605
            error_log('In learnpath::get_link('.$type.','.$item_id.')', 0);
3606
        }
3607
        if (empty($item_id)) {
3608
            if ($this->debug > 2) {
3609
                error_log('In learnpath::get_link() - no item id given in learnpath::get_link()');
3610
                error_log('using current: '.$this->get_current_item_id(), 0);
3611
            }
3612
            $item_id = $this->get_current_item_id();
3613
3614
            if (empty($item_id)) {
3615
                if ($this->debug > 2) {
3616
                    error_log('In learnpath::get_link() - no current item id found in learnpath object', 0);
3617
                }
3618
                //still empty, this means there was no item_id given and we are not in an object context or
3619
                //the object property is empty, return empty link
3620
                $this->first();
3621
3622
                return '';
3623
            }
3624
        }
3625
3626
        $file = '';
3627
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3628
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3629
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3630
        $item_id = (int) $item_id;
3631
3632
        $sql = "SELECT
3633
                    l.lp_type as ltype,
3634
                    l.path as lpath,
3635
                    li.item_type as litype,
3636
                    li.path as lipath,
3637
                    li.parameters as liparams
3638
        		FROM $lp_table l
3639
                INNER JOIN $lp_item_table li
3640
                ON (li.lp_id = l.iid)
3641
        		WHERE 
3642
        		    li.iid = $item_id 
3643
        		";
3644
        if ($this->debug > 2) {
3645
            error_log('In learnpath::get_link() - selecting item '.$sql, 0);
3646
        }
3647
        $res = Database::query($sql);
3648
        if (Database::num_rows($res) > 0) {
3649
            $row = Database::fetch_array($res);
3650
            $lp_type = $row['ltype'];
3651
            $lp_path = $row['lpath'];
3652
            $lp_item_type = $row['litype'];
3653
            $lp_item_path = $row['lipath'];
3654
            $lp_item_params = $row['liparams'];
3655
3656
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3657
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3658
            }
3659
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3660
            if ($type === 'http') {
3661
                //web path
3662
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3663
            } else {
3664
                $course_path = $sys_course_path; //system path
3665
            }
3666
3667
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3668
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3669
            if (in_array(
3670
                $lp_item_type,
3671
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3672
            )
3673
            ) {
3674
                $lp_type = 1;
3675
            }
3676
3677
            if ($this->debug > 2) {
3678
                error_log('In learnpath::get_link() - $lp_type '.$lp_type, 0);
3679
                error_log('In learnpath::get_link() - $lp_item_type '.$lp_item_type, 0);
3680
            }
3681
3682
            // Now go through the specific cases to get the end of the path
3683
            // @todo Use constants instead of int values.
3684
            switch ($lp_type) {
3685
                case 1:
3686
                    $file = self::rl_get_resource_link_for_learnpath(
3687
                        $course_id,
3688
                        $this->get_id(),
3689
                        $item_id,
3690
                        $this->get_view_id()
3691
                    );
3692
                    if ($this->debug > 0) {
3693
                        error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
3694
                    }
3695
                    switch ($lp_item_type) {
3696
                        case 'document':
3697
                            // Shows a button to download the file instead of just downloading the file directly.
3698
                            $documentPathInfo = pathinfo($file);
3699
                            if (isset($documentPathInfo['extension'])) {
3700
                                $parsed = parse_url($documentPathInfo['extension']);
3701
                                if (isset($parsed['path'])) {
3702
                                    $extension = $parsed['path'];
3703
                                    $extensionsToDownload = [
3704
                                        'zip',
3705
                                        'ppt',
3706
                                        'pptx',
3707
                                        'ods',
3708
                                        'xlsx',
3709
                                        'xls',
3710
                                        'csv',
3711
                                        'doc',
3712
                                        'docx',
3713
                                        'dot',
3714
                                    ];
3715
3716
                                    if (in_array($extension, $extensionsToDownload)) {
3717
                                        $file = api_get_path(WEB_CODE_PATH).
3718
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3719
                                    }
3720
                                }
3721
                            }
3722
                            break;
3723
                        case 'dir':
3724
                            $file = 'lp_content.php?type=dir';
3725
                            break;
3726
                        case 'link':
3727
                            if (Link::is_youtube_link($file)) {
3728
                                $src = Link::get_youtube_video_id($file);
3729
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3730
                            } elseif (Link::isVimeoLink($file)) {
3731
                                $src = Link::getVimeoLinkId($file);
3732
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3733
                            } else {
3734
                                // If the current site is HTTPS and the link is
3735
                                // HTTP, browsers will refuse opening the link
3736
                                $urlId = api_get_current_access_url_id();
3737
                                $url = api_get_access_url($urlId, false);
3738
                                $protocol = substr($url['url'], 0, 5);
3739
                                if ($protocol === 'https') {
3740
                                    $linkProtocol = substr($file, 0, 5);
3741
                                    if ($linkProtocol === 'http:') {
3742
                                        //this is the special intervention case
3743
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3744
                                    }
3745
                                }
3746
                            }
3747
                            break;
3748
                        case 'quiz':
3749
                            // Check how much attempts of a exercise exits in lp
3750
                            $lp_item_id = $this->get_current_item_id();
3751
                            $lp_view_id = $this->get_view_id();
3752
3753
                            $prevent_reinit = null;
3754
                            if (isset($this->items[$this->current])) {
3755
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3756
                            }
3757
3758
                            if (empty($provided_toc)) {
3759
                                if ($this->debug > 0) {
3760
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3761
                                }
3762
                                $list = $this->get_toc();
3763
                            } else {
3764
                                if ($this->debug > 0) {
3765
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3766
                                }
3767
                                $list = $provided_toc;
3768
                            }
3769
3770
                            $type_quiz = false;
3771
                            foreach ($list as $toc) {
3772
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3773
                                    $type_quiz = true;
3774
                                }
3775
                            }
3776
3777
                            if ($type_quiz) {
3778
                                $lp_item_id = (int) $lp_item_id;
3779
                                $lp_view_id = (int) $lp_view_id;
3780
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3781
                                        WHERE
3782
                                            c_id = $course_id AND
3783
                                            lp_item_id='".$lp_item_id."' AND
3784
                                            lp_view_id ='".$lp_view_id."' AND
3785
                                            status='completed'";
3786
                                $result = Database::query($sql);
3787
                                $row_count = Database:: fetch_row($result);
3788
                                $count_item_view = (int) $row_count[0];
3789
                                $not_multiple_attempt = 0;
3790
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3791
                                    $not_multiple_attempt = 1;
3792
                                }
3793
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3794
                            }
3795
                            break;
3796
                    }
3797
3798
                    $tmp_array = explode('/', $file);
3799
                    $document_name = $tmp_array[count($tmp_array) - 1];
3800
                    if (strpos($document_name, '_DELETED_')) {
3801
                        $file = 'blank.php?error=document_deleted';
3802
                    }
3803
                    break;
3804
                case 2:
3805
                    if ($this->debug > 2) {
3806
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3807
                    }
3808
3809
                    if ($lp_item_type != 'dir') {
3810
                        // Quite complex here:
3811
                        // We want to make sure 'http://' (and similar) links can
3812
                        // be loaded as is (withouth the Chamilo path in front) but
3813
                        // some contents use this form: resource.htm?resource=http://blablabla
3814
                        // which means we have to find a protocol at the path's start, otherwise
3815
                        // it should not be considered as an external URL.
3816
                        // if ($this->prerequisites_match($item_id)) {
3817
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3818
                            if ($this->debug > 2) {
3819
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3820
                            }
3821
                            // Distant url, return as is.
3822
                            $file = $lp_item_path;
3823
                        } else {
3824
                            if ($this->debug > 2) {
3825
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3826
                            }
3827
                            // Prevent getting untranslatable urls.
3828
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3829
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3830
                            // Prepare the path.
3831
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3832
                            // TODO: Fix this for urls with protocol header.
3833
                            $file = str_replace('//', '/', $file);
3834
                            $file = str_replace(':/', '://', $file);
3835
                            if (substr($lp_path, -1) == '/') {
3836
                                $lp_path = substr($lp_path, 0, -1);
3837
                            }
3838
3839
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3840
                                // if file not found.
3841
                                $decoded = html_entity_decode($lp_item_path);
3842
                                list($decoded) = explode('?', $decoded);
3843
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3844
                                    $file = self::rl_get_resource_link_for_learnpath(
3845
                                        $course_id,
3846
                                        $this->get_id(),
3847
                                        $item_id,
3848
                                        $this->get_view_id()
3849
                                    );
3850
                                    if (empty($file)) {
3851
                                        $file = 'blank.php?error=document_not_found';
3852
                                    } else {
3853
                                        $tmp_array = explode('/', $file);
3854
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3855
                                        if (strpos($document_name, '_DELETED_')) {
3856
                                            $file = 'blank.php?error=document_deleted';
3857
                                        } else {
3858
                                            $file = 'blank.php?error=document_not_found';
3859
                                        }
3860
                                    }
3861
                                } else {
3862
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3863
                                }
3864
                            }
3865
                        }
3866
3867
                        // We want to use parameters if they were defined in the imsmanifest
3868
                        if (strpos($file, 'blank.php') === false) {
3869
                            $lp_item_params = ltrim($lp_item_params, '?');
3870
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3871
                        }
3872
                    } else {
3873
                        $file = 'lp_content.php?type=dir';
3874
                    }
3875
                    break;
3876
                case 3:
3877
                    if ($this->debug > 2) {
3878
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3879
                    }
3880
                    // Formatting AICC HACP append URL.
3881
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3882
                    if (!empty($lp_item_params)) {
3883
                        $aicc_append .= $lp_item_params.'&';
3884
                    }
3885
                    if ($lp_item_type != 'dir') {
3886
                        // Quite complex here:
3887
                        // We want to make sure 'http://' (and similar) links can
3888
                        // be loaded as is (withouth the Chamilo path in front) but
3889
                        // some contents use this form: resource.htm?resource=http://blablabla
3890
                        // which means we have to find a protocol at the path's start, otherwise
3891
                        // it should not be considered as an external URL.
3892
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3893
                            if ($this->debug > 2) {
3894
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3895
                            }
3896
                            // Distant url, return as is.
3897
                            $file = $lp_item_path;
3898
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3899
                            /*
3900
                            if (stristr($file,'<servername>') !== false) {
3901
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3902
                            }
3903
                            */
3904
                            if (stripos($file, '<servername>') !== false) {
3905
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3906
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3907
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3908
                            }
3909
3910
                            $file .= $aicc_append;
3911
                        } else {
3912
                            if ($this->debug > 2) {
3913
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3914
                            }
3915
                            // Prevent getting untranslatable urls.
3916
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3917
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3918
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3919
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3920
                            // TODO: Fix this for urls with protocol header.
3921
                            $file = str_replace('//', '/', $file);
3922
                            $file = str_replace(':/', '://', $file);
3923
                            $file .= $aicc_append;
3924
                        }
3925
                    } else {
3926
                        $file = 'lp_content.php?type=dir';
3927
                    }
3928
                    break;
3929
                case 4:
3930
                    break;
3931
                default:
3932
                    break;
3933
            }
3934
            // Replace &amp; by & because &amp; will break URL with params
3935
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3936
        }
3937
        if ($this->debug > 2) {
3938
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3939
        }
3940
3941
        return $file;
3942
    }
3943
3944
    /**
3945
     * Gets the latest usable view or generate a new one.
3946
     *
3947
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3948
     *
3949
     * @return int DB lp_view id
3950
     */
3951
    public function get_view($attempt_num = 0)
3952
    {
3953
        if ($this->debug > 0) {
3954
            error_log('In learnpath::get_view()', 0);
3955
        }
3956
        $search = '';
3957
        // Use $attempt_num to enable multi-views management (disabled so far).
3958
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3959
            $search = 'AND view_count = '.$attempt_num;
3960
        }
3961
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3962
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3963
3964
        $course_id = api_get_course_int_id();
3965
        $sessionId = api_get_session_id();
3966
3967
        $sql = "SELECT iid, view_count FROM $lp_view_table
3968
        		WHERE
3969
        		    c_id = $course_id AND
3970
        		    lp_id = ".$this->get_id()." AND
3971
        		    user_id = ".$this->get_user_id()." AND
3972
        		    session_id = $sessionId
3973
        		    $search
3974
                ORDER BY view_count DESC";
3975
        $res = Database::query($sql);
3976
        if (Database::num_rows($res) > 0) {
3977
            $row = Database::fetch_array($res);
3978
            $this->lp_view_id = $row['iid'];
3979
        } elseif (!api_is_invitee()) {
3980
            // There is no database record, create one.
3981
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3982
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3983
            Database::query($sql);
3984
            $id = Database::insert_id();
3985
            $this->lp_view_id = $id;
3986
3987
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3988
            Database::query($sql);
3989
        }
3990
3991
        return $this->lp_view_id;
3992
    }
3993
3994
    /**
3995
     * Gets the current view id.
3996
     *
3997
     * @return int View ID (from lp_view)
3998
     */
3999
    public function get_view_id()
4000
    {
4001
        if ($this->debug > 0) {
4002
            error_log('In learnpath::get_view_id()', 0);
4003
        }
4004
        if (!empty($this->lp_view_id)) {
4005
            return $this->lp_view_id;
4006
        } else {
4007
            return 0;
4008
        }
4009
    }
4010
4011
    /**
4012
     * Gets the update queue.
4013
     *
4014
     * @return array Array containing IDs of items to be updated by JavaScript
4015
     */
4016
    public function get_update_queue()
4017
    {
4018
        if ($this->debug > 0) {
4019
            error_log('In learnpath::get_update_queue()', 0);
4020
        }
4021
4022
        return $this->update_queue;
4023
    }
4024
4025
    /**
4026
     * Gets the user ID.
4027
     *
4028
     * @return int User ID
4029
     */
4030
    public function get_user_id()
4031
    {
4032
        if ($this->debug > 0) {
4033
            error_log('In learnpath::get_user_id()', 0);
4034
        }
4035
        if (!empty($this->user_id)) {
4036
            return $this->user_id;
4037
        } else {
4038
            return false;
4039
        }
4040
    }
4041
4042
    /**
4043
     * Checks if any of the items has an audio element attached.
4044
     *
4045
     * @return bool True or false
4046
     */
4047
    public function has_audio()
4048
    {
4049
        if ($this->debug > 1) {
4050
            error_log('In learnpath::has_audio()', 0);
4051
        }
4052
        $has = false;
4053
        foreach ($this->items as $i => $item) {
4054
            if (!empty($this->items[$i]->audio)) {
4055
                $has = true;
4056
                break;
4057
            }
4058
        }
4059
4060
        return $has;
4061
    }
4062
4063
    /**
4064
     * Moves an item up and down at its level.
4065
     *
4066
     * @param int    $id        Item to move up and down
4067
     * @param string $direction Direction 'up' or 'down'
4068
     *
4069
     * @return bool|int
4070
     */
4071
    public function move_item($id, $direction)
4072
    {
4073
        $course_id = api_get_course_int_id();
4074
        if ($this->debug > 0) {
4075
            error_log('In learnpath::move_item('.$id.','.$direction.')', 0);
4076
        }
4077
        if (empty($id) || empty($direction)) {
4078
            return false;
4079
        }
4080
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
4081
        $sql_sel = "SELECT *
4082
                    FROM $tbl_lp_item
4083
                    WHERE  
4084
                        iid = $id
4085
                    ";
4086
        $res_sel = Database::query($sql_sel);
4087
        // Check if elem exists.
4088
        if (Database::num_rows($res_sel) < 1) {
4089
            return false;
4090
        }
4091
        // Gather data.
4092
        $row = Database::fetch_array($res_sel);
4093
        $previous = $row['previous_item_id'];
4094
        $next = $row['next_item_id'];
4095
        $display = $row['display_order'];
4096
        $parent = $row['parent_item_id'];
4097
        $lp = $row['lp_id'];
4098
        // Update the item (switch with previous/next one).
4099
        switch ($direction) {
4100
            case 'up':
4101
                if ($this->debug > 2) {
4102
                    error_log('Movement up detected', 0);
4103
                }
4104
                if ($display > 1) {
4105
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4106
                                 WHERE iid = $previous";
4107
4108
                    if ($this->debug > 2) {
4109
                        error_log('Selecting previous: '.$sql_sel2, 0);
4110
                    }
4111
                    $res_sel2 = Database::query($sql_sel2);
4112
                    if (Database::num_rows($res_sel2) < 1) {
4113
                        $previous_previous = 0;
4114
                    }
4115
                    // Gather data.
4116
                    $row2 = Database::fetch_array($res_sel2);
4117
                    $previous_previous = $row2['previous_item_id'];
4118
                    // Update previous_previous item (switch "next" with current).
4119
                    if ($previous_previous != 0) {
4120
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4121
                                        next_item_id = $id
4122
                                    WHERE iid = $previous_previous";
4123
                        if ($this->debug > 2) {
4124
                            error_log($sql_upd2, 0);
4125
                        }
4126
                        Database::query($sql_upd2);
4127
                    }
4128
                    // Update previous item (switch with current).
4129
                    if ($previous != 0) {
4130
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4131
                                    next_item_id = $next,
4132
                                    previous_item_id = $id,
4133
                                    display_order = display_order +1
4134
                                    WHERE iid = $previous";
4135
                        if ($this->debug > 2) {
4136
                            error_log($sql_upd2, 0);
4137
                        }
4138
                        Database::query($sql_upd2);
4139
                    }
4140
4141
                    // Update current item (switch with previous).
4142
                    if ($id != 0) {
4143
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4144
                                        next_item_id = $previous,
4145
                                        previous_item_id = $previous_previous,
4146
                                        display_order = display_order-1
4147
                                    WHERE c_id = ".$course_id." AND id = $id";
4148
                        if ($this->debug > 2) {
4149
                            error_log($sql_upd2, 0);
4150
                        }
4151
                        Database::query($sql_upd2);
4152
                    }
4153
                    // Update next item (new previous item).
4154
                    if (!empty($next)) {
4155
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4156
                                     WHERE iid = $next";
4157
                        if ($this->debug > 2) {
4158
                            error_log($sql_upd2, 0);
4159
                        }
4160
                        Database::query($sql_upd2);
4161
                    }
4162
                    $display = $display - 1;
4163
                }
4164
                break;
4165
            case 'down':
4166
                if ($this->debug > 2) {
4167
                    error_log('Movement down detected', 0);
4168
                }
4169
                if ($next != 0) {
4170
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item 
4171
                                 WHERE iid = $next";
4172
                    if ($this->debug > 2) {
4173
                        error_log('Selecting next: '.$sql_sel2, 0);
4174
                    }
4175
                    $res_sel2 = Database::query($sql_sel2);
4176
                    if (Database::num_rows($res_sel2) < 1) {
4177
                        $next_next = 0;
4178
                    }
4179
                    // Gather data.
4180
                    $row2 = Database::fetch_array($res_sel2);
4181
                    $next_next = $row2['next_item_id'];
4182
                    // Update previous item (switch with current).
4183
                    if ($previous != 0) {
4184
                        $sql_upd2 = "UPDATE $tbl_lp_item 
4185
                                     SET next_item_id = $next
4186
                                     WHERE iid = $previous";
4187
                        Database::query($sql_upd2);
4188
                    }
4189
                    // Update current item (switch with previous).
4190
                    if ($id != 0) {
4191
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4192
                                     previous_item_id = $next, 
4193
                                     next_item_id = $next_next, 
4194
                                     display_order = display_order + 1
4195
                                     WHERE iid = $id";
4196
                        Database::query($sql_upd2);
4197
                    }
4198
4199
                    // Update next item (new previous item).
4200
                    if ($next != 0) {
4201
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4202
                                     previous_item_id = $previous, 
4203
                                     next_item_id = $id, 
4204
                                     display_order = display_order-1
4205
                                     WHERE iid = $next";
4206
                        Database::query($sql_upd2);
4207
                    }
4208
4209
                    // Update next_next item (switch "previous" with current).
4210
                    if ($next_next != 0) {
4211
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4212
                                     previous_item_id = $id
4213
                                     WHERE iid = $next_next";
4214
                        Database::query($sql_upd2);
4215
                    }
4216
                    $display = $display + 1;
4217
                }
4218
                break;
4219
            default:
4220
                return false;
4221
        }
4222
4223
        return $display;
4224
    }
4225
4226
    /**
4227
     * Move a LP up (display_order).
4228
     *
4229
     * @param int $lp_id      Learnpath ID
4230
     * @param int $categoryId Category ID
4231
     *
4232
     * @return bool
4233
     */
4234
    public static function move_up($lp_id, $categoryId = 0)
4235
    {
4236
        $courseId = api_get_course_int_id();
4237
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4238
4239
        $categoryCondition = '';
4240
        if (!empty($categoryId)) {
4241
            $categoryId = (int) $categoryId;
4242
            $categoryCondition = " AND category_id = $categoryId";
4243
        }
4244
        $sql = "SELECT * FROM $lp_table
4245
                WHERE c_id = $courseId
4246
                $categoryCondition
4247
                ORDER BY display_order";
4248
        $res = Database::query($sql);
4249
        if ($res === false) {
4250
            return false;
4251
        }
4252
4253
        $lps = [];
4254
        $lp_order = [];
4255
        $num = Database::num_rows($res);
4256
        // First check the order is correct, globally (might be wrong because
4257
        // of versions < 1.8.4)
4258
        if ($num > 0) {
4259
            $i = 1;
4260
            while ($row = Database::fetch_array($res)) {
4261
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4262
                    $sql = "UPDATE $lp_table SET display_order = $i
4263
                            WHERE iid = ".$row['iid'];
4264
                    Database::query($sql);
4265
                }
4266
                $row['display_order'] = $i;
4267
                $lps[$row['iid']] = $row;
4268
                $lp_order[$i] = $row['iid'];
4269
                $i++;
4270
            }
4271
        }
4272
        if ($num > 1) { // If there's only one element, no need to sort.
4273
            $order = $lps[$lp_id]['display_order'];
4274
            if ($order > 1) { // If it's the first element, no need to move up.
4275
                $sql = "UPDATE $lp_table SET display_order = $order
4276
                        WHERE iid = ".$lp_order[$order - 1];
4277
                Database::query($sql);
4278
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4279
                        WHERE iid = $lp_id";
4280
                Database::query($sql);
4281
            }
4282
        }
4283
4284
        return true;
4285
    }
4286
4287
    /**
4288
     * Move a learnpath down (display_order).
4289
     *
4290
     * @param int $lp_id      Learnpath ID
4291
     * @param int $categoryId Category ID
4292
     *
4293
     * @return bool
4294
     */
4295
    public static function move_down($lp_id, $categoryId = 0)
4296
    {
4297
        $courseId = api_get_course_int_id();
4298
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4299
4300
        $categoryCondition = '';
4301
        if (!empty($categoryId)) {
4302
            $categoryId = (int) $categoryId;
4303
            $categoryCondition = " AND category_id = $categoryId";
4304
        }
4305
4306
        $sql = "SELECT * FROM $lp_table
4307
                WHERE c_id = $courseId
4308
                $categoryCondition
4309
                ORDER BY display_order";
4310
        $res = Database::query($sql);
4311
        if ($res === false) {
4312
            return false;
4313
        }
4314
        $lps = [];
4315
        $lp_order = [];
4316
        $num = Database::num_rows($res);
4317
        $max = 0;
4318
        // First check the order is correct, globally (might be wrong because
4319
        // of versions < 1.8.4).
4320
        if ($num > 0) {
4321
            $i = 1;
4322
            while ($row = Database::fetch_array($res)) {
4323
                $max = $i;
4324
                if ($row['display_order'] != $i) {
4325
                    // If we find a gap in the order, we need to fix it.
4326
                    $sql = "UPDATE $lp_table SET display_order = $i
4327
                              WHERE iid = ".$row['iid'];
4328
                    Database::query($sql);
4329
                }
4330
                $row['display_order'] = $i;
4331
                $lps[$row['iid']] = $row;
4332
                $lp_order[$i] = $row['iid'];
4333
                $i++;
4334
            }
4335
        }
4336
        if ($num > 1) { // If there's only one element, no need to sort.
4337
            $order = $lps[$lp_id]['display_order'];
4338
            if ($order < $max) { // If it's the first element, no need to move up.
4339
                $sql = "UPDATE $lp_table SET display_order = $order
4340
                        WHERE iid = ".$lp_order[$order + 1];
4341
                Database::query($sql);
4342
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4343
                        WHERE iid = $lp_id";
4344
                Database::query($sql);
4345
            }
4346
        }
4347
4348
        return true;
4349
    }
4350
4351
    /**
4352
     * Updates learnpath attributes to point to the next element
4353
     * The last part is similar to set_current_item but processing the other way around.
4354
     */
4355
    public function next()
4356
    {
4357
        if ($this->debug > 0) {
4358
            error_log('In learnpath::next()', 0);
4359
        }
4360
        $this->last = $this->get_current_item_id();
4361
        $this->items[$this->last]->save(
4362
            false,
4363
            $this->prerequisites_match($this->last)
4364
        );
4365
        $this->autocomplete_parents($this->last);
4366
        $new_index = $this->get_next_index();
4367
        if ($this->debug > 2) {
4368
            error_log('New index: '.$new_index, 0);
4369
        }
4370
        $this->index = $new_index;
4371
        if ($this->debug > 2) {
4372
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4373
        }
4374
        $this->current = $this->ordered_items[$new_index];
4375
        if ($this->debug > 2) {
4376
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4377
        }
4378
    }
4379
4380
    /**
4381
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4382
     * class, this might be redefined to allow several behaviours depending on the document type.
4383
     *
4384
     * @param int $id Resource ID
4385
     */
4386
    public function open($id)
4387
    {
4388
        if ($this->debug > 0) {
4389
            error_log('In learnpath::open()', 0);
4390
        }
4391
        // TODO:
4392
        // set the current resource attribute to this resource
4393
        // switch on element type (redefine in child class?)
4394
        // set status for this item to "opened"
4395
        // start timer
4396
        // initialise score
4397
        $this->index = 0; //or = the last item seen (see $this->last)
4398
    }
4399
4400
    /**
4401
     * Check that all prerequisites are fulfilled. Returns true and an
4402
     * empty string on success, returns false
4403
     * and the prerequisite string on error.
4404
     * This function is based on the rules for aicc_script language as
4405
     * described in the SCORM 1.2 CAM documentation page 108.
4406
     *
4407
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4408
     *
4409
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4410
     *              string otherwise
4411
     */
4412
    public function prerequisites_match($itemId = null)
4413
    {
4414
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4415
        if ($allow) {
4416
            if (api_is_allowed_to_edit() ||
4417
                api_is_platform_admin(true) ||
4418
                api_is_drh() ||
4419
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4420
            ) {
4421
                return true;
4422
            }
4423
        }
4424
4425
        $debug = $this->debug;
4426
        if ($debug > 0) {
4427
            error_log('In learnpath::prerequisites_match()', 0);
4428
        }
4429
4430
        if (empty($itemId)) {
4431
            $itemId = $this->current;
4432
        }
4433
4434
        $currentItem = $this->getItem($itemId);
4435
4436
        if ($currentItem) {
4437
            if ($this->type == 2) {
4438
                // Getting prereq from scorm
4439
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4440
            } else {
4441
                $prereq_string = $currentItem->get_prereq_string();
4442
            }
4443
4444
            if (empty($prereq_string)) {
4445
                if ($debug > 0) {
4446
                    error_log('Found prereq_string is empty return true');
4447
                }
4448
4449
                return true;
4450
            }
4451
            // Clean spaces.
4452
            $prereq_string = str_replace(' ', '', $prereq_string);
4453
            if ($debug > 0) {
4454
                error_log('Found prereq_string: '.$prereq_string, 0);
4455
            }
4456
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4457
            $result = $currentItem->parse_prereq(
4458
                $prereq_string,
4459
                $this->items,
4460
                $this->refs_list,
4461
                $this->get_user_id()
4462
            );
4463
4464
            if ($result === false) {
4465
                $this->set_error_msg($currentItem->prereq_alert);
4466
            }
4467
        } else {
4468
            $result = true;
4469
            if ($debug > 1) {
4470
                error_log('$this->items['.$itemId.'] was not an object', 0);
4471
            }
4472
        }
4473
4474
        if ($debug > 1) {
4475
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4476
        }
4477
4478
        return $result;
4479
    }
4480
4481
    /**
4482
     * Updates learnpath attributes to point to the previous element
4483
     * The last part is similar to set_current_item but processing the other way around.
4484
     */
4485
    public function previous()
4486
    {
4487
        if ($this->debug > 0) {
4488
            error_log('In learnpath::previous()', 0);
4489
        }
4490
        $this->last = $this->get_current_item_id();
4491
        $this->items[$this->last]->save(
4492
            false,
4493
            $this->prerequisites_match($this->last)
4494
        );
4495
        $this->autocomplete_parents($this->last);
4496
        $new_index = $this->get_previous_index();
4497
        $this->index = $new_index;
4498
        $this->current = $this->ordered_items[$new_index];
4499
    }
4500
4501
    /**
4502
     * Publishes a learnpath. This basically means show or hide the learnpath
4503
     * to normal users.
4504
     * Can be used as abstract.
4505
     *
4506
     * @param int $lp_id          Learnpath ID
4507
     * @param int $set_visibility New visibility
4508
     *
4509
     * @return bool
4510
     */
4511
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4512
    {
4513
        $action = 'visible';
4514
        if ($set_visibility != 1) {
4515
            $action = 'invisible';
4516
            self::toggle_publish($lp_id, 'i');
4517
        }
4518
4519
        return api_item_property_update(
4520
            api_get_course_info(),
4521
            TOOL_LEARNPATH,
4522
            $lp_id,
4523
            $action,
4524
            api_get_user_id()
4525
        );
4526
    }
4527
4528
    /**
4529
     * Publishes a learnpath category.
4530
     * This basically means show or hide the learnpath category to normal users.
4531
     *
4532
     * @param int $id
4533
     * @param int $visibility
4534
     *
4535
     * @throws \Doctrine\ORM\NonUniqueResultException
4536
     * @throws \Doctrine\ORM\ORMException
4537
     * @throws \Doctrine\ORM\OptimisticLockException
4538
     * @throws \Doctrine\ORM\TransactionRequiredException
4539
     *
4540
     * @return bool
4541
     */
4542
    public static function toggleCategoryVisibility($id, $visibility = 1)
4543
    {
4544
        $action = 'visible';
4545
4546
        if ($visibility != 1) {
4547
            $action = 'invisible';
4548
            $list = new LearnpathList(
4549
                api_get_user_id(),
4550
                null,
4551
                null,
4552
                null,
4553
                false,
4554
                $id
4555
            );
4556
4557
            $lpList = $list->get_flat_list();
4558
            foreach ($lpList as $lp) {
4559
                self::toggle_visibility($lp['iid'], 0);
4560
            }
4561
4562
            self::toggleCategoryPublish($id, 0);
4563
        }
4564
4565
        return api_item_property_update(
4566
            api_get_course_info(),
4567
            TOOL_LEARNPATH_CATEGORY,
4568
            $id,
4569
            $action,
4570
            api_get_user_id()
4571
        );
4572
    }
4573
4574
    /**
4575
     * Publishes a learnpath. This basically means show or hide the learnpath
4576
     * on the course homepage
4577
     * Can be used as abstract.
4578
     *
4579
     * @param int    $lp_id          Learnpath id
4580
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4581
     *
4582
     * @return bool
4583
     */
4584
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4585
    {
4586
        $course_id = api_get_course_int_id();
4587
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4588
        $lp_id = (int) $lp_id;
4589
        $sql = "SELECT * FROM $tbl_lp
4590
                WHERE iid = $lp_id";
4591
        $result = Database::query($sql);
4592
        if (Database::num_rows($result)) {
4593
            $row = Database::fetch_array($result);
4594
            $name = Database::escape_string($row['name']);
4595
            if ($set_visibility == 'i') {
4596
                $v = 0;
4597
            }
4598
            if ($set_visibility == 'v') {
4599
                $v = 1;
4600
            }
4601
4602
            $session_id = api_get_session_id();
4603
            $session_condition = api_get_session_condition($session_id);
4604
4605
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4606
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4607
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4608
4609
            $sql = "SELECT * FROM $tbl_tool
4610
                    WHERE
4611
                        c_id = $course_id AND
4612
                        (link = '$link' OR link = '$oldLink') AND
4613
                        image = 'scormbuilder.gif' AND
4614
                        (
4615
                            link LIKE '$link%' OR
4616
                            link LIKE '$oldLink%'
4617
                        )
4618
                        $session_condition
4619
                    ";
4620
4621
            $result = Database::query($sql);
4622
            $num = Database::num_rows($result);
4623
            if ($set_visibility == 'i' && $num > 0) {
4624
                $sql = "DELETE FROM $tbl_tool
4625
                        WHERE 
4626
                            c_id = $course_id AND 
4627
                            (link = '$link' OR link = '$oldLink') AND 
4628
                            image='scormbuilder.gif' 
4629
                            $session_condition";
4630
                Database::query($sql);
4631
            } elseif ($set_visibility == 'v' && $num == 0) {
4632
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4633
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4634
                Database::query($sql);
4635
                $insertId = Database::insert_id();
4636
                if ($insertId) {
4637
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4638
                    Database::query($sql);
4639
                }
4640
            } elseif ($set_visibility == 'v' && $num > 0) {
4641
                $sql = "UPDATE $tbl_tool SET
4642
                            c_id = $course_id,
4643
                            name = '$name',
4644
                            link = '$link',
4645
                            image = 'scormbuilder.gif',
4646
                            visibility = '$v',
4647
                            admin = '0',
4648
                            address = 'pastillegris.gif',
4649
                            added_tool = 0,
4650
                            session_id = $session_id
4651
                        WHERE
4652
                            c_id = ".$course_id." AND
4653
                            (link = '$link' OR link = '$oldLink') AND 
4654
                            image='scormbuilder.gif' 
4655
                            $session_condition
4656
                        ";
4657
                Database::query($sql);
4658
            } else {
4659
                // Parameter and database incompatible, do nothing, exit.
4660
                return false;
4661
            }
4662
        } else {
4663
            return false;
4664
        }
4665
    }
4666
4667
    /**
4668
     * Publishes a learnpath.
4669
     * Show or hide the learnpath category on the course homepage.
4670
     *
4671
     * @param int $id
4672
     * @param int $setVisibility
4673
     *
4674
     * @throws \Doctrine\ORM\NonUniqueResultException
4675
     * @throws \Doctrine\ORM\ORMException
4676
     * @throws \Doctrine\ORM\OptimisticLockException
4677
     * @throws \Doctrine\ORM\TransactionRequiredException
4678
     *
4679
     * @return bool
4680
     */
4681
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4682
    {
4683
        $courseId = api_get_course_int_id();
4684
        $sessionId = api_get_session_id();
4685
        $sessionCondition = api_get_session_condition(
4686
            $sessionId,
4687
            true,
4688
            false,
4689
            't.sessionId'
4690
        );
4691
4692
        $em = Database::getManager();
4693
4694
        /** @var CLpCategory $category */
4695
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4696
4697
        if (!$category) {
4698
            return false;
4699
        }
4700
4701
        if (empty($courseId)) {
4702
            return false;
4703
        }
4704
4705
        $link = self::getCategoryLinkForTool($id);
4706
4707
        /** @var CTool $tool */
4708
        $tool = $em->createQuery("
4709
                SELECT t FROM ChamiloCourseBundle:CTool t
4710
                WHERE
4711
                    t.course = :course AND
4712
                    t.link = :link1 AND
4713
                    t.image LIKE 'lp_category.%' AND
4714
                    t.link LIKE :link2
4715
                    $sessionCondition
4716
            ")
4717
            ->setParameters([
4718
                'course' => $courseId,
4719
                'link1' => $link,
4720
                'link2' => "$link%",
4721
            ])
4722
            ->getOneOrNullResult();
4723
4724
        if ($setVisibility == 0 && $tool) {
4725
            $em->remove($tool);
4726
            $em->flush();
4727
4728
            return true;
4729
        }
4730
4731
        if ($setVisibility == 1 && !$tool) {
4732
            $tool = new CTool();
4733
            $tool
4734
                ->setCategory('authoring')
4735
                ->setCourse(api_get_course_entity($courseId))
4736
                ->setName(strip_tags($category->getName()))
4737
                ->setLink($link)
4738
                ->setImage('lp_category.png')
4739
                ->setVisibility(1)
4740
                ->setAdmin(0)
4741
                ->setAddress('pastillegris.gif')
4742
                ->setAddedTool(0)
4743
                ->setSessionId($sessionId)
4744
                ->setTarget('_self');
4745
4746
            $em->persist($tool);
4747
            $em->flush();
4748
4749
            $tool->setId($tool->getIid());
4750
4751
            $em->persist($tool);
4752
            $em->flush();
4753
4754
            return true;
4755
        }
4756
4757
        if ($setVisibility == 1 && $tool) {
4758
            $tool
4759
                ->setName(strip_tags($category->getName()))
4760
                ->setVisibility(1);
4761
4762
            $em->persist($tool);
4763
            $em->flush();
4764
4765
            return true;
4766
        }
4767
4768
        return false;
4769
    }
4770
4771
    /**
4772
     * Check if the learnpath category is visible for a user.
4773
     *
4774
     * @param CLpCategory $category
4775
     * @param User        $user
4776
     * @param int
4777
     * @param int
4778
     *
4779
     * @return bool
4780
     */
4781
    public static function categoryIsVisibleForStudent(
4782
        CLpCategory $category,
4783
        User $user,
4784
        $courseId = 0,
4785
        $sessionId = 0
4786
    ) {
4787
        $subscriptionSettings = self::getSubscriptionSettings();
4788
4789
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4790
            return true;
4791
        }
4792
4793
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4794
4795
        if ($isAllowedToEdit) {
4796
            return true;
4797
        }
4798
4799
        if (empty($category)) {
4800
            return false;
4801
        }
4802
4803
        $users = $category->getUsers();
4804
4805
        if (empty($users) || !$users->count()) {
4806
            return true;
4807
        }
4808
4809
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4810
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4811
4812
        if ($category->hasUserAdded($user)) {
4813
            return true;
4814
        }
4815
4816
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4817
        if (!empty($groups)) {
4818
            $em = Database::getManager();
4819
4820
            /** @var ItemPropertyRepository $itemRepo */
4821
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4822
4823
            /** @var CourseRepository $courseRepo */
4824
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4825
            $session = null;
4826
            if (!empty($sessionId)) {
4827
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4828
            }
4829
4830
            $course = $courseRepo->find($courseId);
4831
4832
            // Subscribed groups to a LP
4833
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4834
                TOOL_LEARNPATH_CATEGORY,
4835
                $category->getId(),
4836
                $course,
4837
                $session
4838
            );
4839
4840
            if (!empty($subscribedGroupsInLp)) {
4841
                $groups = array_column($groups, 'iid');
4842
                /** @var CItemProperty $item */
4843
                foreach ($subscribedGroupsInLp as $item) {
4844
                    if ($item->getGroup() &&
4845
                        in_array($item->getGroup()->getId(), $groups)
4846
                    ) {
4847
                        return true;
4848
                    }
4849
                }
4850
            }
4851
        }
4852
4853
        return false;
4854
    }
4855
4856
    /**
4857
     * Check if a learnpath category is published as course tool.
4858
     *
4859
     * @param CLpCategory $category
4860
     * @param int         $courseId
4861
     *
4862
     * @return bool
4863
     */
4864
    public static function categoryIsPublished(
4865
        CLpCategory $category,
4866
        $courseId
4867
    ) {
4868
        $link = self::getCategoryLinkForTool($category->getId());
4869
        $em = Database::getManager();
4870
4871
        $tools = $em
4872
            ->createQuery("
4873
                SELECT t FROM ChamiloCourseBundle:CTool t
4874
                WHERE t.course = :course AND 
4875
                    t.name = :name AND
4876
                    t.image LIKE 'lp_category.%' AND
4877
                    t.link LIKE :link
4878
            ")
4879
            ->setParameters([
4880
                'course' => $courseId,
4881
                'name' => strip_tags($category->getName()),
4882
                'link' => "$link%",
4883
            ])
4884
            ->getResult();
4885
4886
        /** @var CTool $tool */
4887
        $tool = current($tools);
4888
4889
        return $tool ? $tool->getVisibility() : false;
4890
    }
4891
4892
    /**
4893
     * Restart the whole learnpath. Return the URL of the first element.
4894
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4895
     * To use a similar method  statically, use the create_new_attempt() method.
4896
     *
4897
     * @return bool
4898
     */
4899
    public function restart()
4900
    {
4901
        if ($this->debug > 0) {
4902
            error_log('In learnpath::restart()', 0);
4903
        }
4904
        // TODO
4905
        // Call autosave method to save the current progress.
4906
        //$this->index = 0;
4907
        if (api_is_invitee()) {
4908
            return false;
4909
        }
4910
        $session_id = api_get_session_id();
4911
        $course_id = api_get_course_int_id();
4912
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4913
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4914
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4915
        if ($this->debug > 2) {
4916
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4917
        }
4918
        Database::query($sql);
4919
        $view_id = Database::insert_id();
4920
4921
        if ($view_id) {
4922
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4923
            Database::query($sql);
4924
            $this->lp_view_id = $view_id;
4925
            $this->attempt = $this->attempt + 1;
4926
        } else {
4927
            $this->error = 'Could not insert into item_view table...';
4928
4929
            return false;
4930
        }
4931
        $this->autocomplete_parents($this->current);
4932
        foreach ($this->items as $index => $dummy) {
4933
            $this->items[$index]->restart();
4934
            $this->items[$index]->set_lp_view($this->lp_view_id);
4935
        }
4936
        $this->first();
4937
4938
        return true;
4939
    }
4940
4941
    /**
4942
     * Saves the current item.
4943
     *
4944
     * @return bool
4945
     */
4946
    public function save_current()
4947
    {
4948
        $debug = $this->debug;
4949
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4950
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4951
        if ($debug) {
4952
            error_log('save_current() saving item '.$this->current, 0);
4953
            error_log(''.print_r($this->items, true), 0);
4954
        }
4955
        if (isset($this->items[$this->current]) &&
4956
            is_object($this->items[$this->current])
4957
        ) {
4958
            if ($debug) {
4959
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4960
            }
4961
4962
            $res = $this->items[$this->current]->save(
4963
                false,
4964
                $this->prerequisites_match($this->current)
4965
            );
4966
            $this->autocomplete_parents($this->current);
4967
            $status = $this->items[$this->current]->get_status();
4968
            $this->update_queue[$this->current] = $status;
4969
4970
            if ($debug) {
4971
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4972
            }
4973
4974
            return $res;
4975
        }
4976
4977
        return false;
4978
    }
4979
4980
    /**
4981
     * Saves the given item.
4982
     *
4983
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4984
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4985
     *
4986
     * @return bool
4987
     */
4988
    public function save_item($item_id = null, $from_outside = true)
4989
    {
4990
        $debug = $this->debug;
4991
        if ($debug) {
4992
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4993
        }
4994
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4995
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4996
        if (empty($item_id)) {
4997
            $item_id = (int) $_REQUEST['id'];
4998
        }
4999
5000
        if (empty($item_id)) {
5001
            $item_id = $this->get_current_item_id();
5002
        }
5003
        if (isset($this->items[$item_id]) &&
5004
            is_object($this->items[$item_id])
5005
        ) {
5006
            if ($debug) {
5007
                error_log('Object exists');
5008
            }
5009
5010
            // Saving the item.
5011
            $res = $this->items[$item_id]->save(
5012
                $from_outside,
5013
                $this->prerequisites_match($item_id)
5014
            );
5015
5016
            if ($debug) {
5017
                error_log('update_queue before:');
5018
                error_log(print_r($this->update_queue, 1));
5019
            }
5020
            $this->autocomplete_parents($item_id);
5021
5022
            $status = $this->items[$item_id]->get_status();
5023
            $this->update_queue[$item_id] = $status;
5024
5025
            if ($debug) {
5026
                error_log('get_status(): '.$status);
5027
                error_log('update_queue after:');
5028
                error_log(print_r($this->update_queue, 1));
5029
            }
5030
5031
            return $res;
5032
        }
5033
5034
        return false;
5035
    }
5036
5037
    /**
5038
     * Saves the last item seen's ID only in case.
5039
     */
5040
    public function save_last()
5041
    {
5042
        $course_id = api_get_course_int_id();
5043
        $debug = $this->debug;
5044
        if ($debug) {
5045
            error_log('In learnpath::save_last()', 0);
5046
        }
5047
        $session_condition = api_get_session_condition(
5048
            api_get_session_id(),
5049
            true,
5050
            false
5051
        );
5052
        $table = Database::get_course_table(TABLE_LP_VIEW);
5053
5054
        if (isset($this->current) && !api_is_invitee()) {
5055
            if ($debug) {
5056
                error_log('Saving current item ('.$this->current.') for later review', 0);
5057
            }
5058
            $sql = "UPDATE $table SET
5059
                        last_item = ".intval($this->get_current_item_id())."
5060
                    WHERE
5061
                        c_id = $course_id AND
5062
                        lp_id = ".$this->get_id()." AND
5063
                        user_id = ".$this->get_user_id()." ".$session_condition;
5064
5065
            if ($debug) {
5066
                error_log('Saving last item seen : '.$sql, 0);
5067
            }
5068
            Database::query($sql);
5069
        }
5070
5071
        if (!api_is_invitee()) {
5072
            // Save progress.
5073
            list($progress) = $this->get_progress_bar_text('%');
5074
            if ($progress >= 0 && $progress <= 100) {
5075
                $progress = (int) $progress;
5076
                $sql = "UPDATE $table SET
5077
                            progress = $progress
5078
                        WHERE
5079
                            c_id = $course_id AND
5080
                            lp_id = ".$this->get_id()." AND
5081
                            user_id = ".$this->get_user_id()." ".$session_condition;
5082
                // Ignore errors as some tables might not have the progress field just yet.
5083
                Database::query($sql);
5084
                if ($debug) {
5085
                    error_log($sql);
5086
                }
5087
                $this->progress_db = $progress;
5088
            }
5089
        }
5090
    }
5091
5092
    /**
5093
     * Sets the current item ID (checks if valid and authorized first).
5094
     *
5095
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5096
     */
5097
    public function set_current_item($item_id = null)
5098
    {
5099
        $debug = $this->debug;
5100
        if ($debug) {
5101
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5102
        }
5103
        if (empty($item_id)) {
5104
            if ($debug) {
5105
                error_log('No new current item given, ignore...', 0);
5106
            }
5107
            // Do nothing.
5108
        } else {
5109
            if ($debug) {
5110
                error_log('New current item given is '.$item_id.'...', 0);
5111
            }
5112
            if (is_numeric($item_id)) {
5113
                $item_id = (int) $item_id;
5114
                // TODO: Check in database here.
5115
                $this->last = $this->current;
5116
                $this->current = $item_id;
5117
                // TODO: Update $this->index as well.
5118
                foreach ($this->ordered_items as $index => $item) {
5119
                    if ($item == $this->current) {
5120
                        $this->index = $index;
5121
                        break;
5122
                    }
5123
                }
5124
                if ($debug) {
5125
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5126
                }
5127
            } else {
5128
                if ($debug) {
5129
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5130
                }
5131
            }
5132
        }
5133
    }
5134
5135
    /**
5136
     * Sets the encoding.
5137
     *
5138
     * @param string $enc New encoding
5139
     *
5140
     * @return bool
5141
     *
5142
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5143
     */
5144
    public function set_encoding($enc = 'UTF-8')
5145
    {
5146
        if ($this->debug > 0) {
5147
            error_log('In learnpath::set_encoding()', 0);
5148
        }
5149
5150
        $enc = api_refine_encoding_id($enc);
5151
        if (empty($enc)) {
5152
            $enc = api_get_system_encoding();
5153
        }
5154
        if (api_is_encoding_supported($enc)) {
5155
            $lp = $this->get_id();
5156
            if ($lp != 0) {
5157
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5158
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' 
5159
                        WHERE iid = ".$lp;
5160
                $res = Database::query($sql);
5161
5162
                return $res;
5163
            }
5164
        }
5165
5166
        return false;
5167
    }
5168
5169
    /**
5170
     * Sets the JS lib setting in the database directly.
5171
     * This is the JavaScript library file this lp needs to load on startup.
5172
     *
5173
     * @param string $lib Proximity setting
5174
     *
5175
     * @return bool True on update success. False otherwise.
5176
     */
5177
    public function set_jslib($lib = '')
5178
    {
5179
        if ($this->debug > 0) {
5180
            error_log('In learnpath::set_jslib()', 0);
5181
        }
5182
        $lp = $this->get_id();
5183
5184
        if ($lp != 0) {
5185
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5186
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib' 
5187
                    WHERE iid = $lp";
5188
            $res = Database::query($sql);
5189
5190
            return $res;
5191
        } else {
5192
            return false;
5193
        }
5194
    }
5195
5196
    /**
5197
     * Sets the name of the LP maker (publisher) (and save).
5198
     *
5199
     * @param string $name Optional string giving the new content_maker of this learnpath
5200
     *
5201
     * @return bool True
5202
     */
5203
    public function set_maker($name = '')
5204
    {
5205
        if ($this->debug > 0) {
5206
            error_log('In learnpath::set_maker()', 0);
5207
        }
5208
        if (empty($name)) {
5209
            return false;
5210
        }
5211
        $this->maker = $name;
5212
        $table = Database::get_course_table(TABLE_LP_MAIN);
5213
        $lp_id = $this->get_id();
5214
        $sql = "UPDATE $table SET
5215
                content_maker = '".Database::escape_string($this->maker)."'
5216
                WHERE iid = $lp_id";
5217
        if ($this->debug > 2) {
5218
            error_log('lp updated with new content_maker : '.$this->maker, 0);
5219
        }
5220
        Database::query($sql);
5221
5222
        return true;
5223
    }
5224
5225
    /**
5226
     * Sets the name of the current learnpath (and save).
5227
     *
5228
     * @param string $name Optional string giving the new name of this learnpath
5229
     *
5230
     * @return bool True/False
5231
     */
5232
    public function set_name($name = null)
5233
    {
5234
        if ($this->debug > 0) {
5235
            error_log('In learnpath::set_name()', 0);
5236
        }
5237
        if (empty($name)) {
5238
            return false;
5239
        }
5240
        $this->name = Database::escape_string($name);
5241
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5242
        $lp_id = $this->get_id();
5243
        $course_id = $this->course_info['real_id'];
5244
        $sql = "UPDATE $lp_table SET
5245
                name = '".Database::escape_string($this->name)."'
5246
                WHERE iid = $lp_id";
5247
        if ($this->debug > 2) {
5248
            error_log('lp updated with new name : '.$this->name, 0);
5249
        }
5250
        $result = Database::query($sql);
5251
        // If the lp is visible on the homepage, change his name there.
5252
        if (Database::affected_rows($result)) {
5253
            $session_id = api_get_session_id();
5254
            $session_condition = api_get_session_condition($session_id);
5255
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5256
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5257
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
5258
            	    WHERE
5259
            	        c_id = $course_id AND
5260
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5261
            Database::query($sql);
5262
5263
            return true;
5264
        } else {
5265
            return false;
5266
        }
5267
    }
5268
5269
    /**
5270
     * Set index specified prefix terms for all items in this path.
5271
     *
5272
     * @param string $terms_string Comma-separated list of terms
5273
     * @param string $prefix       Xapian term prefix
5274
     *
5275
     * @return bool False on error, true otherwise
5276
     */
5277
    public function set_terms_by_prefix($terms_string, $prefix)
5278
    {
5279
        $course_id = api_get_course_int_id();
5280
        if (api_get_setting('search_enabled') !== 'true') {
5281
            return false;
5282
        }
5283
5284
        if (!extension_loaded('xapian')) {
5285
            return false;
5286
        }
5287
5288
        $terms_string = trim($terms_string);
5289
        $terms = explode(',', $terms_string);
5290
        array_walk($terms, 'trim_value');
5291
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5292
5293
        // Don't do anything if no change, verify only at DB, not the search engine.
5294
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5295
            return false;
5296
        }
5297
5298
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5299
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5300
5301
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5302
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5303
        $lp_id = (int) $_POST['lp_id'];
5304
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5305
        $result = Database::query($sql);
5306
        $di = new ChamiloIndexer();
5307
5308
        while ($lp_item = Database::fetch_array($result)) {
5309
            // Get search_did.
5310
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5311
            $sql = 'SELECT * FROM %s
5312
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5313
                    LIMIT 1';
5314
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5315
5316
            //echo $sql; echo '<br>';
5317
            $res = Database::query($sql);
5318
            if (Database::num_rows($res) > 0) {
5319
                $se_ref = Database::fetch_array($res);
5320
                // Compare terms.
5321
                $doc = $di->get_document($se_ref['search_did']);
5322
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5323
                $xterms = [];
5324
                foreach ($xapian_terms as $xapian_term) {
5325
                    $xterms[] = substr($xapian_term['name'], 1);
5326
                }
5327
5328
                $dterms = $terms;
5329
                $missing_terms = array_diff($dterms, $xterms);
5330
                $deprecated_terms = array_diff($xterms, $dterms);
5331
5332
                // Save it to search engine.
5333
                foreach ($missing_terms as $term) {
5334
                    $doc->add_term($prefix.$term, 1);
5335
                }
5336
                foreach ($deprecated_terms as $term) {
5337
                    $doc->remove_term($prefix.$term);
5338
                }
5339
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5340
                $di->getDb()->flush();
5341
            }
5342
        }
5343
5344
        return true;
5345
    }
5346
5347
    /**
5348
     * Sets the theme of the LP (local/remote) (and save).
5349
     *
5350
     * @param string $name Optional string giving the new theme of this learnpath
5351
     *
5352
     * @return bool Returns true if theme name is not empty
5353
     */
5354
    public function set_theme($name = '')
5355
    {
5356
        if ($this->debug > 0) {
5357
            error_log('In learnpath::set_theme()', 0);
5358
        }
5359
        $this->theme = $name;
5360
        $table = Database::get_course_table(TABLE_LP_MAIN);
5361
        $lp_id = $this->get_id();
5362
        $sql = "UPDATE $table 
5363
                SET theme = '".Database::escape_string($this->theme)."'
5364
                WHERE iid = $lp_id";
5365
        if ($this->debug > 2) {
5366
            error_log('lp updated with new theme : '.$this->theme, 0);
5367
        }
5368
        Database::query($sql);
5369
5370
        return true;
5371
    }
5372
5373
    /**
5374
     * Sets the image of an LP (and save).
5375
     *
5376
     * @param string $name Optional string giving the new image of this learnpath
5377
     *
5378
     * @return bool Returns true if theme name is not empty
5379
     */
5380
    public function set_preview_image($name = '')
5381
    {
5382
        if ($this->debug > 0) {
5383
            error_log('In learnpath::set_preview_image()', 0);
5384
        }
5385
5386
        $this->preview_image = $name;
5387
        $table = Database::get_course_table(TABLE_LP_MAIN);
5388
        $lp_id = $this->get_id();
5389
        $sql = "UPDATE $table SET
5390
                preview_image = '".Database::escape_string($this->preview_image)."'
5391
                WHERE iid = $lp_id";
5392
        if ($this->debug > 2) {
5393
            error_log('lp updated with new preview image : '.$this->preview_image, 0);
5394
        }
5395
        Database::query($sql);
5396
5397
        return true;
5398
    }
5399
5400
    /**
5401
     * Sets the author of a LP (and save).
5402
     *
5403
     * @param string $name Optional string giving the new author of this learnpath
5404
     *
5405
     * @return bool Returns true if author's name is not empty
5406
     */
5407
    public function set_author($name = '')
5408
    {
5409
        if ($this->debug > 0) {
5410
            error_log('In learnpath::set_author()', 0);
5411
        }
5412
        $this->author = $name;
5413
        $table = Database::get_course_table(TABLE_LP_MAIN);
5414
        $lp_id = $this->get_id();
5415
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5416
                WHERE iid = $lp_id";
5417
        if ($this->debug > 2) {
5418
            error_log('lp updated with new preview author : '.$this->author, 0);
5419
        }
5420
        Database::query($sql);
5421
5422
        return true;
5423
    }
5424
5425
    /**
5426
     * Sets the hide_toc_frame parameter of a LP (and save).
5427
     *
5428
     * @param int $hide 1 if frame is hidden 0 then else
5429
     *
5430
     * @return bool Returns true if author's name is not empty
5431
     */
5432
    public function set_hide_toc_frame($hide)
5433
    {
5434
        if ($this->debug > 0) {
5435
            error_log('In learnpath::set_hide_toc_frame()', 0);
5436
        }
5437
        if (intval($hide) == $hide) {
5438
            $this->hide_toc_frame = $hide;
5439
            $table = Database::get_course_table(TABLE_LP_MAIN);
5440
            $lp_id = $this->get_id();
5441
            $sql = "UPDATE $table SET
5442
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5443
                    WHERE iid = $lp_id";
5444
            if ($this->debug > 2) {
5445
                error_log('lp updated with new preview hide_toc_frame : '.$this->author, 0);
5446
            }
5447
            Database::query($sql);
5448
5449
            return true;
5450
        } else {
5451
            return false;
5452
        }
5453
    }
5454
5455
    /**
5456
     * Sets the prerequisite of a LP (and save).
5457
     *
5458
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5459
     *
5460
     * @return bool returns true if prerequisite is not empty
5461
     */
5462
    public function set_prerequisite($prerequisite)
5463
    {
5464
        if ($this->debug > 0) {
5465
            error_log('In learnpath::set_prerequisite()', 0);
5466
        }
5467
        $this->prerequisite = (int) $prerequisite;
5468
        $table = Database::get_course_table(TABLE_LP_MAIN);
5469
        $lp_id = $this->get_id();
5470
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5471
                WHERE iid = $lp_id";
5472
        if ($this->debug > 2) {
5473
            error_log('lp updated with new preview requisite : '.$this->requisite, 0);
5474
        }
5475
        Database::query($sql);
5476
5477
        return true;
5478
    }
5479
5480
    /**
5481
     * Sets the location/proximity of the LP (local/remote) (and save).
5482
     *
5483
     * @param string $name Optional string giving the new location of this learnpath
5484
     *
5485
     * @return bool True on success / False on error
5486
     */
5487
    public function set_proximity($name = '')
5488
    {
5489
        if ($this->debug > 0) {
5490
            error_log('In learnpath::set_proximity()', 0);
5491
        }
5492
        if (empty($name)) {
5493
            return false;
5494
        }
5495
5496
        $this->proximity = $name;
5497
        $table = Database::get_course_table(TABLE_LP_MAIN);
5498
        $lp_id = $this->get_id();
5499
        $sql = "UPDATE $table SET
5500
                    content_local = '".Database::escape_string($name)."'
5501
                WHERE iid = $lp_id";
5502
        if ($this->debug > 2) {
5503
            error_log('lp updated with new proximity : '.$this->proximity, 0);
5504
        }
5505
        Database::query($sql);
5506
5507
        return true;
5508
    }
5509
5510
    /**
5511
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5512
     *
5513
     * @param int $id DB ID of the item
5514
     */
5515
    public function set_previous_item($id)
5516
    {
5517
        if ($this->debug > 0) {
5518
            error_log('In learnpath::set_previous_item()', 0);
5519
        }
5520
        $this->last = $id;
5521
    }
5522
5523
    /**
5524
     * Sets use_max_score.
5525
     *
5526
     * @param int $use_max_score Optional string giving the new location of this learnpath
5527
     *
5528
     * @return bool True on success / False on error
5529
     */
5530
    public function set_use_max_score($use_max_score = 1)
5531
    {
5532
        if ($this->debug > 0) {
5533
            error_log('In learnpath::set_use_max_score()', 0);
5534
        }
5535
        $use_max_score = (int) $use_max_score;
5536
        $this->use_max_score = $use_max_score;
5537
        $table = Database::get_course_table(TABLE_LP_MAIN);
5538
        $lp_id = $this->get_id();
5539
        $sql = "UPDATE $table SET
5540
                    use_max_score = '".$this->use_max_score."'
5541
                WHERE iid = $lp_id";
5542
5543
        if ($this->debug > 2) {
5544
            error_log('lp updated with new use_max_score : '.$this->use_max_score, 0);
5545
        }
5546
        Database::query($sql);
5547
5548
        return true;
5549
    }
5550
5551
    /**
5552
     * Sets and saves the expired_on date.
5553
     *
5554
     * @param string $expired_on Optional string giving the new author of this learnpath
5555
     *
5556
     * @throws \Doctrine\ORM\OptimisticLockException
5557
     *
5558
     * @return bool Returns true if author's name is not empty
5559
     */
5560
    public function set_expired_on($expired_on)
5561
    {
5562
        if ($this->debug > 0) {
5563
            error_log('In learnpath::set_expired_on()', 0);
5564
        }
5565
5566
        $em = Database::getManager();
5567
        /** @var CLp $lp */
5568
        $lp = $em
5569
            ->getRepository('ChamiloCourseBundle:CLp')
5570
            ->findOneBy(
5571
                [
5572
                    'iid' => $this->get_id(),
5573
                ]
5574
            );
5575
5576
        if (!$lp) {
5577
            return false;
5578
        }
5579
5580
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5581
5582
        $lp->setExpiredOn($this->expired_on);
5583
        $em->persist($lp);
5584
        $em->flush();
5585
5586
        if ($this->debug > 2) {
5587
            error_log('lp updated with new expired_on : '.$this->expired_on, 0);
5588
        }
5589
5590
        return true;
5591
    }
5592
5593
    /**
5594
     * Sets and saves the publicated_on date.
5595
     *
5596
     * @param string $publicated_on Optional string giving the new author of this learnpath
5597
     *
5598
     * @throws \Doctrine\ORM\OptimisticLockException
5599
     *
5600
     * @return bool Returns true if author's name is not empty
5601
     */
5602
    public function set_publicated_on($publicated_on)
5603
    {
5604
        if ($this->debug > 0) {
5605
            error_log('In learnpath::set_expired_on()', 0);
5606
        }
5607
5608
        $em = Database::getManager();
5609
        /** @var CLp $lp */
5610
        $lp = $em
5611
            ->getRepository('ChamiloCourseBundle:CLp')
5612
            ->findOneBy(
5613
                [
5614
                    'iid' => $this->get_id(),
5615
                ]
5616
            );
5617
5618
        if (!$lp) {
5619
            return false;
5620
        }
5621
5622
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5623
        $lp->setPublicatedOn($this->publicated_on);
5624
        $em->persist($lp);
5625
        $em->flush();
5626
5627
        if ($this->debug > 2) {
5628
            error_log('lp updated with new publicated_on : '.$this->publicated_on, 0);
5629
        }
5630
5631
        return true;
5632
    }
5633
5634
    /**
5635
     * Sets and saves the expired_on date.
5636
     *
5637
     * @return bool Returns true if author's name is not empty
5638
     */
5639
    public function set_modified_on()
5640
    {
5641
        if ($this->debug > 0) {
5642
            error_log('In learnpath::set_expired_on()', 0);
5643
        }
5644
        $this->modified_on = api_get_utc_datetime();
5645
        $table = Database::get_course_table(TABLE_LP_MAIN);
5646
        $lp_id = $this->get_id();
5647
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5648
                WHERE iid = $lp_id";
5649
        if ($this->debug > 2) {
5650
            error_log('lp updated with new expired_on : '.$this->modified_on, 0);
5651
        }
5652
        Database::query($sql);
5653
5654
        return true;
5655
    }
5656
5657
    /**
5658
     * Sets the object's error message.
5659
     *
5660
     * @param string $error Error message. If empty, reinits the error string
5661
     */
5662
    public function set_error_msg($error = '')
5663
    {
5664
        if ($this->debug > 0) {
5665
            error_log('In learnpath::set_error_msg()', 0);
5666
        }
5667
        if (empty($error)) {
5668
            $this->error = '';
5669
        } else {
5670
            $this->error .= $error;
5671
        }
5672
    }
5673
5674
    /**
5675
     * Launches the current item if not 'sco'
5676
     * (starts timer and make sure there is a record ready in the DB).
5677
     *
5678
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5679
     *
5680
     * @return bool
5681
     */
5682
    public function start_current_item($allow_new_attempt = false)
5683
    {
5684
        $debug = $this->debug;
5685
        if ($debug) {
5686
            error_log('In learnpath::start_current_item()');
5687
            error_log('current: '.$this->current);
5688
        }
5689
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5690
            $type = $this->get_type();
5691
            $item_type = $this->items[$this->current]->get_type();
5692
            if (($type == 2 && $item_type != 'sco') ||
5693
                ($type == 3 && $item_type != 'au') ||
5694
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5695
            ) {
5696
                if ($debug) {
5697
                    error_log('item type: '.$item_type);
5698
                    error_log('lp type: '.$type);
5699
                }
5700
                $this->items[$this->current]->open($allow_new_attempt);
5701
                $this->autocomplete_parents($this->current);
5702
                $prereq_check = $this->prerequisites_match($this->current);
5703
                if ($debug) {
5704
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5705
                }
5706
                $this->items[$this->current]->save(false, $prereq_check);
5707
            }
5708
            // If sco, then it is supposed to have been updated by some other call.
5709
            if ($item_type == 'sco') {
5710
                $this->items[$this->current]->restart();
5711
            }
5712
        }
5713
        if ($debug) {
5714
            error_log('lp_view_session_id');
5715
            error_log($this->lp_view_session_id);
5716
            error_log('api session id');
5717
            error_log(api_get_session_id());
5718
            error_log('End of learnpath::start_current_item()');
5719
        }
5720
5721
        return true;
5722
    }
5723
5724
    /**
5725
     * Stops the processing and counters for the old item (as held in $this->last).
5726
     *
5727
     * @return bool True/False
5728
     */
5729
    public function stop_previous_item()
5730
    {
5731
        $debug = $this->debug;
5732
        if ($debug) {
5733
            error_log('In learnpath::stop_previous_item()', 0);
5734
        }
5735
5736
        if ($this->last != 0 && $this->last != $this->current &&
5737
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5738
        ) {
5739
            if ($debug) {
5740
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5741
            }
5742
            switch ($this->get_type()) {
5743
                case '3':
5744
                    if ($this->items[$this->last]->get_type() != 'au') {
5745
                        if ($debug) {
5746
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5747
                        }
5748
                        $this->items[$this->last]->close();
5749
                    } else {
5750
                        if ($debug) {
5751
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5752
                        }
5753
                    }
5754
                    break;
5755
                case '2':
5756
                    if ($this->items[$this->last]->get_type() != 'sco') {
5757
                        if ($debug) {
5758
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5759
                        }
5760
                        $this->items[$this->last]->close();
5761
                    } else {
5762
                        if ($debug) {
5763
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5764
                        }
5765
                    }
5766
                    break;
5767
                case '1':
5768
                default:
5769
                    if ($debug) {
5770
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5771
                    }
5772
                    $this->items[$this->last]->close();
5773
                    break;
5774
            }
5775
        } else {
5776
            if ($debug) {
5777
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5778
            }
5779
5780
            return false;
5781
        }
5782
5783
        return true;
5784
    }
5785
5786
    /**
5787
     * Updates the default view mode from fullscreen to embedded and inversely.
5788
     *
5789
     * @return string The current default view mode ('fullscreen' or 'embedded')
5790
     */
5791
    public function update_default_view_mode()
5792
    {
5793
        if ($this->debug > 0) {
5794
            error_log('In learnpath::update_default_view_mode()', 0);
5795
        }
5796
        $table = Database::get_course_table(TABLE_LP_MAIN);
5797
        $sql = "SELECT * FROM $table
5798
                WHERE iid = ".$this->get_id();
5799
        $res = Database::query($sql);
5800
        if (Database::num_rows($res) > 0) {
5801
            $row = Database::fetch_array($res);
5802
            $default_view_mode = $row['default_view_mod'];
5803
            $view_mode = $default_view_mode;
5804
            switch ($default_view_mode) {
5805
                case 'fullscreen': // default with popup
5806
                    $view_mode = 'embedded';
5807
                    break;
5808
                case 'embedded': // default view with left menu
5809
                    $view_mode = 'embedframe';
5810
                    break;
5811
                case 'embedframe': //folded menu
5812
                    $view_mode = 'impress';
5813
                    break;
5814
                case 'impress':
5815
                    $view_mode = 'fullscreen';
5816
                    break;
5817
            }
5818
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5819
                    WHERE iid = ".$this->get_id();
5820
            Database::query($sql);
5821
            $this->mode = $view_mode;
5822
5823
            return $view_mode;
5824
        } else {
5825
            if ($this->debug > 2) {
5826
                error_log('Problem in update_default_view() - could not find LP '.$this->get_id().' in DB', 0);
5827
            }
5828
        }
5829
5830
        return -1;
5831
    }
5832
5833
    /**
5834
     * Updates the default behaviour about auto-commiting SCORM updates.
5835
     *
5836
     * @return bool True if auto-commit has been set to 'on', false otherwise
5837
     */
5838
    public function update_default_scorm_commit()
5839
    {
5840
        if ($this->debug > 0) {
5841
            error_log('In learnpath::update_default_scorm_commit()', 0);
5842
        }
5843
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5844
        $sql = "SELECT * FROM $lp_table
5845
                WHERE iid = ".$this->get_id();
5846
        $res = Database::query($sql);
5847
        if (Database::num_rows($res) > 0) {
5848
            $row = Database::fetch_array($res);
5849
            $force = $row['force_commit'];
5850
            if ($force == 1) {
5851
                $force = 0;
5852
                $force_return = false;
5853
            } elseif ($force == 0) {
5854
                $force = 1;
5855
                $force_return = true;
5856
            }
5857
            $sql = "UPDATE $lp_table SET force_commit = $force
5858
                    WHERE iid = ".$this->get_id();
5859
            Database::query($sql);
5860
            $this->force_commit = $force_return;
5861
5862
            return $force_return;
5863
        } else {
5864
            if ($this->debug > 2) {
5865
                error_log('Problem in update_default_scorm_commit() - could not find LP '.$this->get_id().' in DB', 0);
5866
            }
5867
        }
5868
5869
        return -1;
5870
    }
5871
5872
    /**
5873
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5874
     *
5875
     * @return bool True on success, false on failure
5876
     */
5877
    public function update_display_order()
5878
    {
5879
        $course_id = api_get_course_int_id();
5880
        $table = Database::get_course_table(TABLE_LP_MAIN);
5881
        $sql = "SELECT * FROM $table 
5882
                WHERE c_id = $course_id 
5883
                ORDER BY display_order";
5884
        $res = Database::query($sql);
5885
        if ($res === false) {
5886
            return false;
5887
        }
5888
5889
        $num = Database::num_rows($res);
5890
        // First check the order is correct, globally (might be wrong because
5891
        // of versions < 1.8.4).
5892
        if ($num > 0) {
5893
            $i = 1;
5894
            while ($row = Database::fetch_array($res)) {
5895
                if ($row['display_order'] != $i) {
5896
                    // If we find a gap in the order, we need to fix it.
5897
                    $sql = "UPDATE $table SET display_order = $i
5898
                            WHERE iid = ".$row['iid'];
5899
                    Database::query($sql);
5900
                }
5901
                $i++;
5902
            }
5903
        }
5904
5905
        return true;
5906
    }
5907
5908
    /**
5909
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5910
     *
5911
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5912
     */
5913
    public function update_reinit()
5914
    {
5915
        if ($this->debug > 0) {
5916
            error_log('In learnpath::update_reinit()', 0);
5917
        }
5918
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5919
        $sql = "SELECT * FROM $lp_table
5920
                WHERE iid = ".$this->get_id();
5921
        $res = Database::query($sql);
5922
        if (Database::num_rows($res) > 0) {
5923
            $row = Database::fetch_array($res);
5924
            $force = $row['prevent_reinit'];
5925
            if ($force == 1) {
5926
                $force = 0;
5927
            } elseif ($force == 0) {
5928
                $force = 1;
5929
            }
5930
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5931
                    WHERE iid = ".$this->get_id();
5932
            Database::query($sql);
5933
            $this->prevent_reinit = $force;
5934
5935
            return $force;
5936
        } else {
5937
            if ($this->debug > 2) {
5938
                error_log('Problem in update_reinit() - could not find LP '.$this->get_id().' in DB', 0);
5939
            }
5940
        }
5941
5942
        return -1;
5943
    }
5944
5945
    /**
5946
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5947
     *
5948
     * @return string 'single', 'multi' or 'seriousgame'
5949
     *
5950
     * @author ndiechburg <[email protected]>
5951
     */
5952
    public function get_attempt_mode()
5953
    {
5954
        //Set default value for seriousgame_mode
5955
        if (!isset($this->seriousgame_mode)) {
5956
            $this->seriousgame_mode = 0;
5957
        }
5958
        // Set default value for prevent_reinit
5959
        if (!isset($this->prevent_reinit)) {
5960
            $this->prevent_reinit = 1;
5961
        }
5962
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5963
            return 'seriousgame';
5964
        }
5965
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5966
            return 'single';
5967
        }
5968
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5969
            return 'multiple';
5970
        }
5971
5972
        return 'single';
5973
    }
5974
5975
    /**
5976
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5977
     *
5978
     * @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...
5979
     *
5980
     * @return bool
5981
     *
5982
     * @author ndiechburg <[email protected]>
5983
     */
5984
    public function set_attempt_mode($mode)
5985
    {
5986
        switch ($mode) {
5987
            case 'seriousgame':
5988
                $sg_mode = 1;
5989
                $prevent_reinit = 1;
5990
                break;
5991
            case 'single':
5992
                $sg_mode = 0;
5993
                $prevent_reinit = 1;
5994
                break;
5995
            case 'multiple':
5996
                $sg_mode = 0;
5997
                $prevent_reinit = 0;
5998
                break;
5999
            default:
6000
                $sg_mode = 0;
6001
                $prevent_reinit = 0;
6002
                break;
6003
        }
6004
        $this->prevent_reinit = $prevent_reinit;
6005
        $this->seriousgame_mode = $sg_mode;
6006
        $table = Database::get_course_table(TABLE_LP_MAIN);
6007
        $sql = "UPDATE $table SET
6008
                prevent_reinit = $prevent_reinit ,
6009
                seriousgame_mode = $sg_mode
6010
                WHERE iid = ".$this->get_id();
6011
        $res = Database::query($sql);
6012
        if ($res) {
6013
            return true;
6014
        } else {
6015
            return false;
6016
        }
6017
    }
6018
6019
    /**
6020
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
6021
     *
6022
     * @author ndiechburg <[email protected]>
6023
     */
6024
    public function switch_attempt_mode()
6025
    {
6026
        if ($this->debug > 0) {
6027
            error_log('In learnpath::switch_attempt_mode()', 0);
6028
        }
6029
        $mode = $this->get_attempt_mode();
6030
        switch ($mode) {
6031
            case 'single':
6032
                $next_mode = 'multiple';
6033
                break;
6034
            case 'multiple':
6035
                $next_mode = 'seriousgame';
6036
                break;
6037
            case 'seriousgame':
6038
                $next_mode = 'single';
6039
                break;
6040
            default:
6041
                $next_mode = 'single';
6042
                break;
6043
        }
6044
        $this->set_attempt_mode($next_mode);
6045
    }
6046
6047
    /**
6048
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
6049
     * but possibility to do again a completed item.
6050
     *
6051
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
6052
     *
6053
     * @author ndiechburg <[email protected]>
6054
     */
6055
    public function set_seriousgame_mode()
6056
    {
6057
        if ($this->debug > 0) {
6058
            error_log('In learnpath::set_seriousgame_mode()', 0);
6059
        }
6060
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6061
        $sql = "SELECT * FROM $lp_table 
6062
                WHERE iid = ".$this->get_id();
6063
        $res = Database::query($sql);
6064
        if (Database::num_rows($res) > 0) {
6065
            $row = Database::fetch_array($res);
6066
            $force = $row['seriousgame_mode'];
6067
            if ($force == 1) {
6068
                $force = 0;
6069
            } elseif ($force == 0) {
6070
                $force = 1;
6071
            }
6072
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
6073
			        WHERE iid = ".$this->get_id();
6074
            Database::query($sql);
6075
            $this->seriousgame_mode = $force;
6076
6077
            return $force;
6078
        } else {
6079
            if ($this->debug > 2) {
6080
                error_log('Problem in set_seriousgame_mode() - could not find LP '.$this->get_id().' in DB', 0);
6081
            }
6082
        }
6083
6084
        return -1;
6085
    }
6086
6087
    /**
6088
     * Updates the "scorm_debug" value that shows or hide the debug window.
6089
     *
6090
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
6091
     */
6092
    public function update_scorm_debug()
6093
    {
6094
        if ($this->debug > 0) {
6095
            error_log('In learnpath::update_scorm_debug()', 0);
6096
        }
6097
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
6098
        $sql = "SELECT * FROM $lp_table
6099
                WHERE iid = ".$this->get_id();
6100
        $res = Database::query($sql);
6101
        if (Database::num_rows($res) > 0) {
6102
            $row = Database::fetch_array($res);
6103
            $force = $row['debug'];
6104
            if ($force == 1) {
6105
                $force = 0;
6106
            } elseif ($force == 0) {
6107
                $force = 1;
6108
            }
6109
            $sql = "UPDATE $lp_table SET debug = $force
6110
                    WHERE iid = ".$this->get_id();
6111
            Database::query($sql);
6112
            $this->scorm_debug = $force;
6113
6114
            return $force;
6115
        } else {
6116
            if ($this->debug > 2) {
6117
                error_log('Problem in update_scorm_debug() - could not find LP '.$this->get_id().' in DB', 0);
6118
            }
6119
        }
6120
6121
        return -1;
6122
    }
6123
6124
    /**
6125
     * Function that makes a call to the function sort_tree_array and create_tree_array.
6126
     *
6127
     * @author Kevin Van Den Haute
6128
     *
6129
     * @param  array
6130
     */
6131
    public function tree_array($array)
6132
    {
6133
        if ($this->debug > 1) {
6134
            error_log('In learnpath::tree_array()', 0);
6135
        }
6136
        $array = $this->sort_tree_array($array);
6137
        $this->create_tree_array($array);
6138
    }
6139
6140
    /**
6141
     * Creates an array with the elements of the learning path tree in it.
6142
     *
6143
     * @author Kevin Van Den Haute
6144
     *
6145
     * @param array $array
6146
     * @param int   $parent
6147
     * @param int   $depth
6148
     * @param array $tmp
6149
     */
6150
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
6151
    {
6152
        if ($this->debug > 1) {
6153
            error_log('In learnpath::create_tree_array())', 0);
6154
        }
6155
6156
        if (is_array($array)) {
6157
            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...
6158
                if ($array[$i]['parent_item_id'] == $parent) {
6159
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6160
                        $tmp[] = $array[$i]['parent_item_id'];
6161
                        $depth++;
6162
                    }
6163
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6164
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6165
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6166
6167
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6168
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6169
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6170
                    $this->arrMenu[] = [
6171
                        'id' => $array[$i]['id'],
6172
                        'ref' => $ref,
6173
                        'item_type' => $array[$i]['item_type'],
6174
                        'title' => $array[$i]['title'],
6175
                        'path' => $path,
6176
                        'description' => $array[$i]['description'],
6177
                        'parent_item_id' => $array[$i]['parent_item_id'],
6178
                        'previous_item_id' => $array[$i]['previous_item_id'],
6179
                        'next_item_id' => $array[$i]['next_item_id'],
6180
                        'min_score' => $array[$i]['min_score'],
6181
                        'max_score' => $array[$i]['max_score'],
6182
                        'mastery_score' => $array[$i]['mastery_score'],
6183
                        'display_order' => $array[$i]['display_order'],
6184
                        'prerequisite' => $preq,
6185
                        'depth' => $depth,
6186
                        'audio' => $audio,
6187
                        'prerequisite_min_score' => $prerequisiteMinScore,
6188
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6189
                    ];
6190
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6191
                }
6192
            }
6193
        }
6194
    }
6195
6196
    /**
6197
     * Sorts a multi dimensional array by parent id and display order.
6198
     *
6199
     * @author Kevin Van Den Haute
6200
     *
6201
     * @param array $array (array with al the learning path items in it)
6202
     *
6203
     * @return array
6204
     */
6205
    public function sort_tree_array($array)
6206
    {
6207
        foreach ($array as $key => $row) {
6208
            $parent[$key] = $row['parent_item_id'];
6209
            $position[$key] = $row['display_order'];
6210
        }
6211
6212
        if (count($array) > 0) {
6213
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6214
        }
6215
6216
        return $array;
6217
    }
6218
6219
    /**
6220
     * Function that creates a html list of learning path items so that we can add audio files to them.
6221
     *
6222
     * @author Kevin Van Den Haute
6223
     *
6224
     * @return string
6225
     */
6226
    public function overview()
6227
    {
6228
        if ($this->debug > 0) {
6229
            error_log('In learnpath::overview()', 0);
6230
        }
6231
6232
        $return = '';
6233
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6234
6235
        // we need to start a form when we want to update all the mp3 files
6236
        if ($update_audio == 'true') {
6237
            $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">';
6238
        }
6239
        $return .= '<div id="message"></div>';
6240
        if (count($this->items) == 0) {
6241
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6242
        } else {
6243
            $return_audio = '<table class="data_table">';
6244
            $return_audio .= '<tr>';
6245
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6246
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6247
            $return_audio .= '</tr>';
6248
6249
            if ($update_audio != 'true') {
6250
                $return .= '<div class="col-md-12">';
6251
                $return .= self::return_new_tree($update_audio);
6252
                $return .= '</div>';
6253
                $return .= Display::div(
6254
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6255
                    ['style' => 'float:left; margin-top:15px;width:100%']
6256
                );
6257
            } else {
6258
                $return_audio .= self::return_new_tree($update_audio);
6259
                $return .= $return_audio.'</table>';
6260
            }
6261
6262
            // We need to close the form when we are updating the mp3 files.
6263
            if ($update_audio == 'true') {
6264
                $return .= '<div class="footer-audio">';
6265
                $return .= Display::button(
6266
                    'save_audio',
6267
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6268
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6269
                );
6270
                $return .= '</div>';
6271
            }
6272
        }
6273
6274
        // We need to close the form when we are updating the mp3 files.
6275
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6276
            $return .= '</form>';
6277
        }
6278
6279
        return $return;
6280
    }
6281
6282
    /**
6283
     * @param string $update_audio
6284
     *
6285
     * @return array
6286
     */
6287
    public function processBuildMenuElements($update_audio = 'false')
6288
    {
6289
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6290
        $course_id = api_get_course_int_id();
6291
        $table = Database::get_course_table(TABLE_LP_ITEM);
6292
6293
        $sql = "SELECT * FROM $table
6294
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
6295
6296
        $result = Database::query($sql);
6297
        $arrLP = [];
6298
        while ($row = Database::fetch_array($result)) {
6299
            $arrLP[] = [
6300
                'id' => $row['iid'],
6301
                'item_type' => $row['item_type'],
6302
                'title' => Security::remove_XSS($row['title']),
6303
                'path' => $row['path'],
6304
                'description' => Security::remove_XSS($row['description']),
6305
                'parent_item_id' => $row['parent_item_id'],
6306
                'previous_item_id' => $row['previous_item_id'],
6307
                'next_item_id' => $row['next_item_id'],
6308
                'max_score' => $row['max_score'],
6309
                'min_score' => $row['min_score'],
6310
                'mastery_score' => $row['mastery_score'],
6311
                'prerequisite' => $row['prerequisite'],
6312
                'display_order' => $row['display_order'],
6313
                'audio' => $row['audio'],
6314
                'prerequisite_max_score' => $row['prerequisite_max_score'],
6315
                'prerequisite_min_score' => $row['prerequisite_min_score'],
6316
            ];
6317
        }
6318
6319
        $this->tree_array($arrLP);
6320
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6321
        unset($this->arrMenu);
6322
        $default_data = null;
6323
        $default_content = null;
6324
        $elements = [];
6325
        $return_audio = null;
6326
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6327
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6328
6329
        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...
6330
            $title = $arrLP[$i]['title'];
6331
            $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6332
6333
            // Link for the documents
6334
            if ($arrLP[$i]['item_type'] == 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6335
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6336
                $title_cut = Display::url(
6337
                    $title_cut,
6338
                    $url,
6339
                    [
6340
                        'class' => 'ajax moved',
6341
                        'data-title' => $title,
6342
                        'title' => $title,
6343
                    ]
6344
                );
6345
            }
6346
6347
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6348
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6349
                Session::write('pathItem', $arrLP[$i]['path']);
6350
            }
6351
6352
            if (($i % 2) == 0) {
6353
                $oddClass = 'row_odd';
6354
            } else {
6355
                $oddClass = 'row_even';
6356
            }
6357
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6358
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6359
6360
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6361
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6362
            } else {
6363
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6364
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6365
                } else {
6366
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6367
                        $icon = Display::return_icon('certificate.png');
6368
                    } else {
6369
                        $icon = Display::return_icon('folder_document.png');
6370
                    }
6371
                }
6372
            }
6373
6374
            // The audio column.
6375
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6376
            $audio = '';
6377
            if (!$update_audio || $update_audio != 'true') {
6378
                if (empty($arrLP[$i]['audio'])) {
6379
                    $audio .= '';
6380
                }
6381
            } else {
6382
                $types = self::getChapterTypes();
6383
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6384
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6385
                    if (!empty($arrLP[$i]['audio'])) {
6386
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6387
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6388
                    }
6389
                }
6390
            }
6391
6392
            $return_audio .= Display::span($icon.' '.$title).
6393
                Display::tag(
6394
                    'td',
6395
                    $audio,
6396
                    ['style' => '']
6397
                );
6398
            $return_audio .= '</td>';
6399
            $move_icon = '';
6400
            $move_item_icon = '';
6401
            $edit_icon = '';
6402
            $delete_icon = '';
6403
            $audio_icon = '';
6404
            $prerequisities_icon = '';
6405
            $forumIcon = '';
6406
            $previewIcon = '';
6407
            $pluginCalendarIcon = '';
6408
6409
            $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6410
            $plugin = null;
6411
            if ($pluginCalendar) {
6412
                $plugin = LearningCalendarPlugin::create();
6413
            }
6414
6415
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6416
6417
            if ($is_allowed_to_edit) {
6418
                if (!$update_audio || $update_audio != 'true') {
6419
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6420
                        $move_icon .= '<a class="moved" href="#">';
6421
                        $move_icon .= Display::return_icon(
6422
                            'move_everywhere.png',
6423
                            get_lang('Move'),
6424
                            [],
6425
                            ICON_SIZE_TINY
6426
                        );
6427
                        $move_icon .= '</a>';
6428
                    }
6429
                }
6430
6431
                // No edit for this item types
6432
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6433
                    if ($arrLP[$i]['item_type'] != 'dir') {
6434
                        $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">';
6435
                        $edit_icon .= Display::return_icon(
6436
                            'edit.png',
6437
                            get_lang('LearnpathEditModule'),
6438
                            [],
6439
                            ICON_SIZE_TINY
6440
                        );
6441
                        $edit_icon .= '</a>';
6442
6443
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6444
                            $forumThread = null;
6445
                            if (isset($this->items[$arrLP[$i]['id']])) {
6446
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6447
                                    $this->course_int_id,
6448
                                    $this->lp_session_id
6449
                                );
6450
                            }
6451
                            if ($forumThread) {
6452
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6453
                                        'action' => 'dissociate_forum',
6454
                                        'id' => $arrLP[$i]['id'],
6455
                                        'lp_id' => $this->lp_id,
6456
                                    ]);
6457
                                $forumIcon = Display::url(
6458
                                    Display::return_icon(
6459
                                        'forum.png',
6460
                                        get_lang('DissociateForumToLPItem'),
6461
                                        [],
6462
                                        ICON_SIZE_TINY
6463
                                    ),
6464
                                    $forumIconUrl,
6465
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6466
                                );
6467
                            } else {
6468
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6469
                                        'action' => 'create_forum',
6470
                                        'id' => $arrLP[$i]['id'],
6471
                                        'lp_id' => $this->lp_id,
6472
                                    ]);
6473
                                $forumIcon = Display::url(
6474
                                    Display::return_icon(
6475
                                        'forum.png',
6476
                                        get_lang('AssociateForumToLPItem'),
6477
                                        [],
6478
                                        ICON_SIZE_TINY
6479
                                    ),
6480
                                    $forumIconUrl,
6481
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6482
                                );
6483
                            }
6484
                        }
6485
                    } else {
6486
                        $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">';
6487
                        $edit_icon .= Display::return_icon(
6488
                            'edit.png',
6489
                            get_lang('LearnpathEditModule'),
6490
                            [],
6491
                            ICON_SIZE_TINY
6492
                        );
6493
                        $edit_icon .= '</a>';
6494
                    }
6495
                } else {
6496
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6497
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6498
                        $edit_icon .= Display::return_icon(
6499
                            'edit.png',
6500
                            get_lang('Edit'),
6501
                            [],
6502
                            ICON_SIZE_TINY
6503
                        );
6504
                        $edit_icon .= '</a>';
6505
                    }
6506
                }
6507
6508
                if ($pluginCalendar) {
6509
                    $pluginLink = $pluginUrl.
6510
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6511
6512
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6513
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6514
                    if ($itemInfo && $itemInfo['value'] == 1) {
6515
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6516
                    }
6517
                    $pluginCalendarIcon = Display::url(
6518
                        $iconCalendar,
6519
                        $pluginLink,
6520
                        ['class' => 'btn btn-default']
6521
                    );
6522
                }
6523
6524
                $delete_icon .= ' <a 
6525
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6526
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6527
                    class="btn btn-default">';
6528
                $delete_icon .= Display::return_icon(
6529
                    'delete.png',
6530
                    get_lang('LearnpathDeleteModule'),
6531
                    [],
6532
                    ICON_SIZE_TINY
6533
                );
6534
                $delete_icon .= '</a>';
6535
6536
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6537
                $previewImage = Display::return_icon(
6538
                    'preview_view.png',
6539
                    get_lang('Preview'),
6540
                    [],
6541
                    ICON_SIZE_TINY
6542
                );
6543
6544
                switch ($arrLP[$i]['item_type']) {
6545
                    case TOOL_DOCUMENT:
6546
                    case TOOL_LP_FINAL_ITEM:
6547
                    case TOOL_READOUT_TEXT:
6548
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6549
                        $previewIcon = Display::url(
6550
                            $previewImage,
6551
                            $urlPreviewLink,
6552
                            [
6553
                                'target' => '_blank',
6554
                                'class' => 'btn btn-default',
6555
                                'data-title' => $arrLP[$i]['title'],
6556
                                'title' => $arrLP[$i]['title'],
6557
                            ]
6558
                        );
6559
                        break;
6560
                    case TOOL_THREAD:
6561
                    case TOOL_FORUM:
6562
                    case TOOL_QUIZ:
6563
                    case TOOL_STUDENTPUBLICATION:
6564
                    case TOOL_LP_FINAL_ITEM:
6565
                    case TOOL_LINK:
6566
                        //$target = '';
6567
                        //$class = 'btn btn-default ajax';
6568
                        //if ($arrLP[$i]['item_type'] == TOOL_LINK) {
6569
                        $class = 'btn btn-default';
6570
                        $target = '_blank';
6571
                        //}
6572
6573
                        $link = self::rl_get_resource_link_for_learnpath(
6574
                            $this->course_int_id,
6575
                            $this->lp_id,
6576
                            $arrLP[$i]['id'],
6577
                            0
6578
                        );
6579
                        $previewIcon = Display::url(
6580
                            $previewImage,
6581
                            $link,
6582
                            [
6583
                                'class' => $class,
6584
                                'data-title' => $arrLP[$i]['title'],
6585
                                'title' => $arrLP[$i]['title'],
6586
                                'target' => $target,
6587
                            ]
6588
                        );
6589
                        break;
6590
                    default:
6591
                        $previewIcon = Display::url(
6592
                            $previewImage,
6593
                            $url.'&action=view_item',
6594
                            ['class' => 'btn btn-default', 'target' => '_blank']
6595
                        );
6596
                        break;
6597
                }
6598
6599
                if ($arrLP[$i]['item_type'] != 'dir') {
6600
                    $prerequisities_icon = Display::url(
6601
                        Display::return_icon(
6602
                            'accept.png',
6603
                            get_lang('LearnpathPrerequisites'),
6604
                            [],
6605
                            ICON_SIZE_TINY
6606
                        ),
6607
                        $url.'&action=edit_item_prereq',
6608
                        ['class' => 'btn btn-default']
6609
                    );
6610
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6611
                        $move_item_icon = Display::url(
6612
                            Display::return_icon(
6613
                                'move.png',
6614
                                get_lang('Move'),
6615
                                [],
6616
                                ICON_SIZE_TINY
6617
                            ),
6618
                            $url.'&action=move_item',
6619
                            ['class' => 'btn btn-default']
6620
                        );
6621
                    }
6622
                    $audio_icon = Display::url(
6623
                        Display::return_icon(
6624
                            'audio.png',
6625
                            get_lang('UplUpload'),
6626
                            [],
6627
                            ICON_SIZE_TINY
6628
                        ),
6629
                        $url.'&action=add_audio',
6630
                        ['class' => 'btn btn-default']
6631
                    );
6632
                }
6633
            }
6634
            if ($update_audio != 'true') {
6635
                $row = $move_icon.' '.$icon.
6636
                    Display::span($title_cut).
6637
                    Display::tag(
6638
                        'div',
6639
                        "<div class=\"btn-group btn-group-xs\">
6640
                                    $previewIcon 
6641
                                    $audio 
6642
                                    $edit_icon 
6643
                                    $pluginCalendarIcon
6644
                                    $forumIcon 
6645
                                    $prerequisities_icon 
6646
                                    $move_item_icon 
6647
                                    $audio_icon 
6648
                                    $delete_icon
6649
                                </div>",
6650
                        ['class' => 'btn-toolbar button_actions']
6651
                    );
6652
            } else {
6653
                $row =
6654
                    Display::span($title.$icon).
6655
                    Display::span($audio, ['class' => 'button_actions']);
6656
            }
6657
6658
            $parent_id = $arrLP[$i]['parent_item_id'];
6659
            $default_data[$arrLP[$i]['id']] = $row;
6660
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6661
6662
            if (empty($parent_id)) {
6663
                $elements[$arrLP[$i]['id']]['data'] = $row;
6664
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6665
            } else {
6666
                $parent_arrays = [];
6667
                if ($arrLP[$i]['depth'] > 1) {
6668
                    //Getting list of parents
6669
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6670
                        foreach ($arrLP as $item) {
6671
                            if ($item['id'] == $parent_id) {
6672
                                if ($item['parent_item_id'] == 0) {
6673
                                    $parent_id = $item['id'];
6674
                                    break;
6675
                                } else {
6676
                                    $parent_id = $item['parent_item_id'];
6677
                                    if (empty($parent_arrays)) {
6678
                                        $parent_arrays[] = intval($item['id']);
6679
                                    }
6680
                                    $parent_arrays[] = $parent_id;
6681
                                    break;
6682
                                }
6683
                            }
6684
                        }
6685
                    }
6686
                }
6687
6688
                if (!empty($parent_arrays)) {
6689
                    $parent_arrays = array_reverse($parent_arrays);
6690
                    $val = '$elements';
6691
                    $x = 0;
6692
                    foreach ($parent_arrays as $item) {
6693
                        if ($x != count($parent_arrays) - 1) {
6694
                            $val .= '["'.$item.'"]["children"]';
6695
                        } else {
6696
                            $val .= '["'.$item.'"]["children"]';
6697
                        }
6698
                        $x++;
6699
                    }
6700
                    $val .= "";
6701
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6702
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6703
                } else {
6704
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6705
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6706
                }
6707
            }
6708
        }
6709
6710
        return [
6711
            'elements' => $elements,
6712
            'default_data' => $default_data,
6713
            'default_content' => $default_content,
6714
            'return_audio' => $return_audio,
6715
        ];
6716
    }
6717
6718
    /**
6719
     * @param string $updateAudio true/false strings
6720
     *
6721
     * @return string
6722
     */
6723
    public function returnLpItemList($updateAudio)
6724
    {
6725
        $result = $this->processBuildMenuElements($updateAudio);
6726
6727
        $html = self::print_recursive(
6728
            $result['elements'],
6729
            $result['default_data'],
6730
            $result['default_content']
6731
        );
6732
6733
        if (!empty($html)) {
6734
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6735
        }
6736
6737
        return $html;
6738
    }
6739
6740
    /**
6741
     * @param string $update_audio
6742
     * @param bool   $drop_element_here
6743
     *
6744
     * @return string
6745
     */
6746
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6747
    {
6748
        $return = '';
6749
        $result = $this->processBuildMenuElements($update_audio);
6750
6751
        $list = '<ul id="lp_item_list">';
6752
        $tree = $this->print_recursive(
6753
            $result['elements'],
6754
            $result['default_data'],
6755
            $result['default_content']
6756
        );
6757
6758
        if (!empty($tree)) {
6759
            $list .= $tree;
6760
        } else {
6761
            if ($drop_element_here) {
6762
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6763
            }
6764
        }
6765
        $list .= '</ul>';
6766
6767
        $return .= Display::panelCollapse(
6768
            $this->name,
6769
            $list,
6770
            'scorm-list',
6771
            null,
6772
            'scorm-list-accordion',
6773
            'scorm-list-collapse'
6774
        );
6775
6776
        if ($update_audio === 'true') {
6777
            $return = $result['return_audio'];
6778
        }
6779
6780
        return $return;
6781
    }
6782
6783
    /**
6784
     * @param array $elements
6785
     * @param array $default_data
6786
     * @param array $default_content
6787
     *
6788
     * @return string
6789
     */
6790
    public function print_recursive($elements, $default_data, $default_content)
6791
    {
6792
        $return = '';
6793
        foreach ($elements as $key => $item) {
6794
            if (isset($item['load_data']) || empty($item['data'])) {
6795
                $item['data'] = $default_data[$item['load_data']];
6796
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6797
            }
6798
            $sub_list = '';
6799
            if (isset($item['type']) && $item['type'] == 'dir') {
6800
                // empty value
6801
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6802
            }
6803
            if (empty($item['children'])) {
6804
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6805
                $active = null;
6806
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6807
                    $active = 'active';
6808
                }
6809
                $return .= Display::tag(
6810
                    'li',
6811
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6812
                    ['id' => $key, 'class' => 'record li_container']
6813
                );
6814
            } else {
6815
                // Sections
6816
                $data = '';
6817
                if (isset($item['children'])) {
6818
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6819
                }
6820
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6821
                $return .= Display::tag(
6822
                    'li',
6823
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6824
                    ['id' => $key, 'class' => 'record li_container']
6825
                );
6826
            }
6827
        }
6828
6829
        return $return;
6830
    }
6831
6832
    /**
6833
     * This function builds the action menu.
6834
     *
6835
     * @param bool $returnContent          Optional
6836
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6837
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6838
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6839
     *
6840
     * @return string
6841
     */
6842
    public function build_action_menu(
6843
        $returnContent = false,
6844
        $showRequirementButtons = true,
6845
        $isConfigPage = false,
6846
        $allowExpand = true
6847
    ) {
6848
        $actionsLeft = '';
6849
        $actionsRight = '';
6850
        $actionsLeft .= Display::url(
6851
            Display::return_icon(
6852
                'back.png',
6853
                get_lang('ReturnToLearningPaths'),
6854
                '',
6855
                ICON_SIZE_MEDIUM
6856
            ),
6857
            'lp_controller.php?'.api_get_cidreq()
6858
        );
6859
        $actionsLeft .= Display::url(
6860
            Display::return_icon(
6861
                'preview_view.png',
6862
                get_lang('Preview'),
6863
                '',
6864
                ICON_SIZE_MEDIUM
6865
            ),
6866
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6867
                'action' => 'view',
6868
                'lp_id' => $this->lp_id,
6869
                'isStudentView' => 'true',
6870
            ])
6871
        );
6872
6873
        $actionsLeft .= Display::url(
6874
            Display::return_icon(
6875
                'upload_audio.png',
6876
                get_lang('UpdateAllAudioFragments'),
6877
                '',
6878
                ICON_SIZE_MEDIUM
6879
            ),
6880
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6881
                'action' => 'admin_view',
6882
                'lp_id' => $this->lp_id,
6883
                'updateaudio' => 'true',
6884
            ])
6885
        );
6886
6887
        if (!$isConfigPage) {
6888
            $actionsLeft .= Display::url(
6889
                Display::return_icon(
6890
                    'settings.png',
6891
                    get_lang('CourseSettings'),
6892
                    '',
6893
                    ICON_SIZE_MEDIUM
6894
                ),
6895
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6896
                    'action' => 'edit',
6897
                    'lp_id' => $this->lp_id,
6898
                ])
6899
            );
6900
        } else {
6901
            $actionsLeft .= Display::url(
6902
                Display::return_icon(
6903
                    'edit.png',
6904
                    get_lang('Edit'),
6905
                    '',
6906
                    ICON_SIZE_MEDIUM
6907
                ),
6908
                'lp_controller.php?'.http_build_query([
6909
                    'action' => 'build',
6910
                    'lp_id' => $this->lp_id,
6911
                ]).'&'.api_get_cidreq()
6912
            );
6913
        }
6914
6915
        if ($allowExpand) {
6916
            $actionsLeft .= Display::url(
6917
                Display::return_icon(
6918
                    'expand.png',
6919
                    get_lang('Expand'),
6920
                    ['id' => 'expand'],
6921
                    ICON_SIZE_MEDIUM
6922
                ).
6923
                Display::return_icon(
6924
                    'contract.png',
6925
                    get_lang('Collapse'),
6926
                    ['id' => 'contract', 'class' => 'hide'],
6927
                    ICON_SIZE_MEDIUM
6928
                ),
6929
                '#',
6930
                ['role' => 'button', 'id' => 'hide_bar_template']
6931
            );
6932
        }
6933
6934
        if ($showRequirementButtons) {
6935
            $buttons = [
6936
                [
6937
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6938
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6939
                        'action' => 'set_previous_step_as_prerequisite',
6940
                        'lp_id' => $this->lp_id,
6941
                    ]),
6942
                ],
6943
                [
6944
                    'title' => get_lang('ClearAllPrerequisites'),
6945
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6946
                        'action' => 'clear_prerequisites',
6947
                        'lp_id' => $this->lp_id,
6948
                    ]),
6949
                ],
6950
            ];
6951
            $actionsRight = Display::groupButtonWithDropDown(
6952
                get_lang('PrerequisitesOptions'),
6953
                $buttons,
6954
                true
6955
            );
6956
        }
6957
6958
        $toolbar = Display::toolbarAction(
6959
            'actions-lp-controller',
6960
            [$actionsLeft, $actionsRight]
6961
        );
6962
6963
        if ($returnContent) {
6964
            return $toolbar;
6965
        }
6966
6967
        echo $toolbar;
6968
    }
6969
6970
    /**
6971
     * Creates the default learning path folder.
6972
     *
6973
     * @param array $course
6974
     * @param int   $creatorId
6975
     *
6976
     * @return bool
6977
     */
6978
    public static function generate_learning_path_folder($course, $creatorId = 0)
6979
    {
6980
        // Creating learning_path folder
6981
        $dir = '/learning_path';
6982
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6983
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6984
6985
        $folder = false;
6986
        $folderData = create_unexisting_directory(
6987
            $course,
6988
            $creatorId,
6989
            0,
6990
            null,
6991
            0,
6992
            $filepath,
6993
            $dir,
6994
            get_lang('LearningPaths'),
6995
            0
6996
        );
6997
6998
        if (!empty($folderData)) {
6999
            $folder = true;
7000
        }
7001
7002
        return $folder;
7003
    }
7004
7005
    /**
7006
     * @param array  $course
7007
     * @param string $lp_name
7008
     * @param int    $creatorId
7009
     *
7010
     * @return array
7011
     */
7012
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
7013
    {
7014
        $filepath = '';
7015
        $dir = '/learning_path/';
7016
7017
        if (empty($lp_name)) {
7018
            $lp_name = $this->name;
7019
        }
7020
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7021
7022
        $folder = self::generate_learning_path_folder($course, $creatorId);
7023
7024
        // Limits title size
7025
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
7026
        $dir = $dir.$title;
7027
7028
        // Creating LP folder
7029
        $documentId = null;
7030
        if ($folder) {
7031
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
7032
            $folderData = create_unexisting_directory(
7033
                $course,
7034
                $creatorId,
7035
                0,
7036
                0,
7037
                0,
7038
                $filepath,
7039
                $dir,
7040
                $lp_name
7041
            );
7042
            if (!empty($folderData)) {
7043
                $folder = true;
7044
            }
7045
7046
            $documentId = $folderData->getIid();
7047
            /*} else {
7048
                $folder = true;
7049
            }*/
7050
7051
            $dir = $dir.'/';
7052
            if ($folder) {
7053
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
7054
            }
7055
        }
7056
7057
        /*if (empty($documentId)) {
7058
            $dir = api_remove_trailing_slash($dir);
7059
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
7060
        }*/
7061
7062
        $array = [
7063
            'dir' => $dir,
7064
            'filepath' => $filepath,
7065
            'folder' => $folder,
7066
            'id' => $documentId,
7067
        ];
7068
7069
        return $array;
7070
    }
7071
7072
    /**
7073
     * Create a new document //still needs some finetuning.
7074
     *
7075
     * @param array  $courseInfo
7076
     * @param string $content
7077
     * @param string $title
7078
     * @param string $extension
7079
     * @param int    $parentId
7080
     * @param int    $creatorId  creator id
7081
     *
7082
     * @return int
7083
     */
7084
    public function create_document(
7085
        $courseInfo,
7086
        $content = '',
7087
        $title = '',
7088
        $extension = 'html',
7089
        $parentId = 0,
7090
        $creatorId = 0
7091
    ) {
7092
        if (!empty($courseInfo)) {
7093
            $course_id = $courseInfo['real_id'];
7094
        } else {
7095
            $course_id = api_get_course_int_id();
7096
        }
7097
7098
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7099
        $sessionId = api_get_session_id();
7100
7101
        // Generates folder
7102
        $result = $this->generate_lp_folder($courseInfo);
7103
        $dir = $result['dir'];
7104
7105
        if (empty($parentId) || $parentId == '/') {
7106
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7107
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7108
7109
            if ($parentId === '/') {
7110
                $dir = '/';
7111
            }
7112
7113
            // Please, do not modify this dirname formatting.
7114
            if (strstr($dir, '..')) {
7115
                $dir = '/';
7116
            }
7117
7118
            if (!empty($dir[0]) && $dir[0] == '.') {
7119
                $dir = substr($dir, 1);
7120
            }
7121
            if (!empty($dir[0]) && $dir[0] != '/') {
7122
                $dir = '/'.$dir;
7123
            }
7124
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7125
                $dir .= '/';
7126
            }
7127
        } else {
7128
            $parentInfo = DocumentManager::get_document_data_by_id(
7129
                $parentId,
7130
                $courseInfo['code']
7131
            );
7132
            if (!empty($parentInfo)) {
7133
                $dir = $parentInfo['path'].'/';
7134
            }
7135
        }
7136
7137
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7138
        if (!is_dir($filepath)) {
7139
            $dir = '/';
7140
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7141
        }
7142
7143
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7144
        // is already escaped twice when it gets here.
7145
7146
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7147
        if (!empty($title)) {
7148
            $title = api_replace_dangerous_char(stripslashes($title));
7149
        } else {
7150
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7151
        }
7152
7153
        $title = disable_dangerous_file($title);
7154
        $filename = $title;
7155
        $content = !empty($content) ? $content : $_POST['content_lp'];
7156
        $tmp_filename = $filename;
7157
7158
        $i = 0;
7159
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7160
            $tmp_filename = $filename.'_'.++$i;
7161
        }
7162
7163
        $filename = $tmp_filename.'.'.$extension;
7164
        if ($extension == 'html') {
7165
            $content = stripslashes($content);
7166
            $content = str_replace(
7167
                api_get_path(WEB_COURSE_PATH),
7168
                api_get_path(REL_PATH).'courses/',
7169
                $content
7170
            );
7171
7172
            // Change the path of mp3 to absolute.
7173
7174
            // The first regexp deals with :// urls.
7175
            $content = preg_replace(
7176
                "|(flashvars=\"file=)([^:/]+)/|",
7177
                "$1".api_get_path(
7178
                    REL_COURSE_PATH
7179
                ).$courseInfo['path'].'/document/',
7180
                $content
7181
            );
7182
            // The second regexp deals with audio/ urls.
7183
            $content = preg_replace(
7184
                "|(flashvars=\"file=)([^/]+)/|",
7185
                "$1".api_get_path(
7186
                    REL_COURSE_PATH
7187
                ).$courseInfo['path'].'/document/$2/',
7188
                $content
7189
            );
7190
            // For flv player: To prevent edition problem with firefox,
7191
            // we have to use a strange tip (don't blame me please).
7192
            $content = str_replace(
7193
                '</body>',
7194
                '<style type="text/css">body{}</style></body>',
7195
                $content
7196
            );
7197
        }
7198
7199
        if (!file_exists($filepath.$filename)) {
7200
            if ($fp = @fopen($filepath.$filename, 'w')) {
7201
                fputs($fp, $content);
7202
                fclose($fp);
7203
7204
                $file_size = filesize($filepath.$filename);
7205
                $save_file_path = $dir.$filename;
7206
7207
                $document = DocumentManager::addDocument(
7208
                    $courseInfo,
7209
                    $save_file_path,
7210
                    'file',
7211
                    $file_size,
7212
                    $tmp_filename,
7213
                    '',
7214
                    0, //readonly
7215
                    true,
7216
                    null,
7217
                    $sessionId,
7218
                    $creatorId
7219
                );
7220
7221
                $document_id = $document->getId();
7222
7223
                if ($document_id) {
7224
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7225
                    $new_title = $originalTitle;
7226
7227
                    if ($new_comment || $new_title) {
7228
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7229
                        $ct = '';
7230
                        if ($new_comment) {
7231
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7232
                        }
7233
                        if ($new_title) {
7234
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7235
                        }
7236
7237
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7238
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7239
                        Database::query($sql);
7240
                    }
7241
                }
7242
7243
                return $document_id;
7244
            }
7245
        }
7246
    }
7247
7248
    /**
7249
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7250
     *
7251
     * @param array $_course array
7252
     */
7253
    public function edit_document($_course)
7254
    {
7255
        $course_id = api_get_course_int_id();
7256
        $urlAppend = api_get_configuration_value('url_append');
7257
        // Please, do not modify this dirname formatting.
7258
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7259
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7260
7261
        if (strstr($dir, '..')) {
7262
            $dir = '/';
7263
        }
7264
7265
        if (isset($dir[0]) && $dir[0] == '.') {
7266
            $dir = substr($dir, 1);
7267
        }
7268
7269
        if (isset($dir[0]) && $dir[0] != '/') {
7270
            $dir = '/'.$dir;
7271
        }
7272
7273
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7274
            $dir .= '/';
7275
        }
7276
7277
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7278
        if (!is_dir($filepath)) {
7279
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7280
        }
7281
7282
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7283
7284
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7285
            $document_id = (int) $_POST['path'];
7286
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7287
            if (empty($documentInfo)) {
7288
                // Try with iid
7289
                $table = Database::get_course_table(TABLE_DOCUMENT);
7290
                $sql = "SELECT id, path FROM $table 
7291
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7292
                $res_doc = Database::query($sql);
7293
                $row = Database::fetch_array($res_doc);
7294
                if ($row) {
7295
                    $document_id = $row['id'];
7296
                    $documentPath = $row['path'];
7297
                }
7298
            } else {
7299
                $documentPath = $documentInfo['path'];
7300
            }
7301
7302
            $content = stripslashes($_POST['content_lp']);
7303
            $file = $filepath.$documentPath;
7304
7305
            if (!file_exists($file)) {
7306
                return false;
7307
            }
7308
7309
            if ($fp = @fopen($file, 'w')) {
7310
                $content = str_replace(
7311
                    api_get_path(WEB_COURSE_PATH),
7312
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7313
                    $content
7314
                );
7315
                // Change the path of mp3 to absolute.
7316
                // The first regexp deals with :// urls.
7317
                $content = preg_replace(
7318
                    "|(flashvars=\"file=)([^:/]+)/|",
7319
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7320
                    $content
7321
                );
7322
                // The second regexp deals with audio/ urls.
7323
                $content = preg_replace(
7324
                    "|(flashvars=\"file=)([^:/]+)/|",
7325
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7326
                    $content
7327
                );
7328
                fputs($fp, $content);
7329
                fclose($fp);
7330
7331
                $sql = "UPDATE $table_doc SET
7332
                            title='".Database::escape_string($_POST['title'])."'
7333
                        WHERE c_id = $course_id AND id = ".$document_id;
7334
                Database::query($sql);
7335
            }
7336
        }
7337
    }
7338
7339
    /**
7340
     * Displays the selected item, with a panel for manipulating the item.
7341
     *
7342
     * @param int    $item_id
7343
     * @param string $msg
7344
     * @param bool   $show_actions
7345
     *
7346
     * @return string
7347
     */
7348
    public function display_item($item_id, $msg = null, $show_actions = true)
7349
    {
7350
        $course_id = api_get_course_int_id();
7351
        $return = '';
7352
        if (is_numeric($item_id)) {
7353
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7354
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7355
                    WHERE lp.iid = ".intval($item_id);
7356
            $result = Database::query($sql);
7357
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7358
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7359
7360
                // Prevents wrong parent selection for document, see Bug#1251.
7361
                if ($row['item_type'] != 'dir') {
7362
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7363
                }
7364
7365
                if ($show_actions) {
7366
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7367
                }
7368
                $return .= '<div style="padding:10px;">';
7369
7370
                if ($msg != '') {
7371
                    $return .= $msg;
7372
                }
7373
7374
                $return .= '<h3>'.$row['title'].'</h3>';
7375
7376
                switch ($row['item_type']) {
7377
                    case TOOL_THREAD:
7378
                        $link = $this->rl_get_resource_link_for_learnpath(
7379
                            $course_id,
7380
                            $row['lp_id'],
7381
                            $item_id,
7382
                            0
7383
                        );
7384
                        $return .= Display::url(
7385
                            get_lang('GoToThread'),
7386
                            $link,
7387
                            ['class' => 'btn btn-primary']
7388
                        );
7389
                        break;
7390
                    case TOOL_FORUM:
7391
                        $return .= Display::url(
7392
                            get_lang('GoToForum'),
7393
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7394
                            ['class' => 'btn btn-primary']
7395
                        );
7396
                        break;
7397
                    case TOOL_QUIZ:
7398
                        if (!empty($row['path'])) {
7399
                            $exercise = new Exercise();
7400
                            $exercise->read($row['path']);
7401
                            $return .= $exercise->description.'<br />';
7402
                            $return .= Display::url(
7403
                                get_lang('GoToExercise'),
7404
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7405
                                ['class' => 'btn btn-primary']
7406
                            );
7407
                        }
7408
                        break;
7409
                    case TOOL_LP_FINAL_ITEM:
7410
                        $return .= $this->getSavedFinalItem();
7411
                        break;
7412
                    case TOOL_DOCUMENT:
7413
                    case TOOL_READOUT_TEXT:
7414
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7415
                        $sql_doc = "SELECT path FROM $tbl_doc
7416
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7417
                        $result = Database::query($sql_doc);
7418
                        $path_file = Database::result($result, 0, 0);
7419
                        $path_parts = pathinfo($path_file);
7420
                        // TODO: Correct the following naive comparisons.
7421
                        if (in_array($path_parts['extension'], [
7422
                            'html',
7423
                            'txt',
7424
                            'png',
7425
                            'jpg',
7426
                            'JPG',
7427
                            'jpeg',
7428
                            'JPEG',
7429
                            'gif',
7430
                            'swf',
7431
                            'pdf',
7432
                            'htm',
7433
                        ])) {
7434
                            $return .= $this->display_document($row['path'], true, true);
7435
                        }
7436
                        break;
7437
                    case TOOL_HOTPOTATOES:
7438
                        $return .= $this->display_document($row['path'], false, true);
7439
                        break;
7440
                }
7441
                $return .= '</div>';
7442
            }
7443
        }
7444
7445
        return $return;
7446
    }
7447
7448
    /**
7449
     * Shows the needed forms for editing a specific item.
7450
     *
7451
     * @param int $item_id
7452
     *
7453
     * @throws Exception
7454
     * @throws HTML_QuickForm_Error
7455
     *
7456
     * @return string
7457
     */
7458
    public function display_edit_item($item_id)
7459
    {
7460
        $course_id = api_get_course_int_id();
7461
        $return = '';
7462
        $item_id = (int) $item_id;
7463
7464
        if (empty($item_id)) {
7465
            return '';
7466
        }
7467
7468
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7469
        $sql = "SELECT * FROM $tbl_lp_item
7470
                WHERE iid = ".$item_id;
7471
        $res = Database::query($sql);
7472
        $row = Database::fetch_array($res);
7473
        switch ($row['item_type']) {
7474
            case 'dir':
7475
            case 'asset':
7476
            case 'sco':
7477
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7478
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7479
                    $return .= $this->display_item_form(
7480
                        $row['item_type'],
7481
                        get_lang('EditCurrentChapter').' :',
7482
                        'edit',
7483
                        $item_id,
7484
                        $row
7485
                    );
7486
                } else {
7487
                    $return .= $this->display_item_form(
7488
                        $row['item_type'],
7489
                        get_lang('EditCurrentChapter').' :',
7490
                        'edit_item',
7491
                        $item_id,
7492
                        $row
7493
                    );
7494
                }
7495
                break;
7496
            case TOOL_DOCUMENT:
7497
            case TOOL_READOUT_TEXT:
7498
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7499
                $sql = "SELECT lp.*, doc.path as dir
7500
                        FROM $tbl_lp_item as lp
7501
                        LEFT JOIN $tbl_doc as doc
7502
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7503
                        WHERE
7504
                            doc.c_id = $course_id AND
7505
                            lp.iid = ".$item_id;
7506
                $res_step = Database::query($sql);
7507
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7508
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7509
7510
                if ($row['item_type'] === TOOL_DOCUMENT) {
7511
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7512
                }
7513
7514
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7515
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7516
                }
7517
                break;
7518
            case TOOL_LINK:
7519
                $linkId = (int) $row['path'];
7520
                if (!empty($linkId)) {
7521
                    $table = Database::get_course_table(TABLE_LINK);
7522
                    $sql = 'SELECT url FROM '.$table.'
7523
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7524
                    $res_link = Database::query($sql);
7525
                    $row_link = Database::fetch_array($res_link);
7526
                    if (empty($row_link)) {
7527
                        // Try with id
7528
                        $sql = 'SELECT url FROM '.$table.'
7529
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7530
                        $res_link = Database::query($sql);
7531
                        $row_link = Database::fetch_array($res_link);
7532
                    }
7533
7534
                    if (is_array($row_link)) {
7535
                        $row['url'] = $row_link['url'];
7536
                    }
7537
                }
7538
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7539
                $return .= $this->display_link_form('edit', $item_id, $row);
7540
                break;
7541
            case TOOL_LP_FINAL_ITEM:
7542
                Session::write('finalItem', true);
7543
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7544
                $sql = "SELECT lp.*, doc.path as dir
7545
                        FROM $tbl_lp_item as lp
7546
                        LEFT JOIN $tbl_doc as doc
7547
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7548
                        WHERE
7549
                            doc.c_id = $course_id AND
7550
                            lp.iid = ".$item_id;
7551
                $res_step = Database::query($sql);
7552
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7553
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7554
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7555
                break;
7556
            case TOOL_QUIZ:
7557
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7558
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7559
                break;
7560
            case TOOL_HOTPOTATOES:
7561
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7562
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7563
                break;
7564
            case TOOL_STUDENTPUBLICATION:
7565
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7566
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7567
                break;
7568
            case TOOL_FORUM:
7569
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7570
                $return .= $this->display_forum_form('edit', $item_id, $row);
7571
                break;
7572
            case TOOL_THREAD:
7573
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7574
                $return .= $this->display_thread_form('edit', $item_id, $row);
7575
                break;
7576
        }
7577
7578
        return $return;
7579
    }
7580
7581
    /**
7582
     * Function that displays a list with al the resources that
7583
     * could be added to the learning path.
7584
     *
7585
     * @throws Exception
7586
     * @throws HTML_QuickForm_Error
7587
     *
7588
     * @return bool
7589
     */
7590
    public function display_resources()
7591
    {
7592
        $course_code = api_get_course_id();
7593
7594
        // Get all the docs.
7595
        $documents = $this->get_documents(true);
7596
7597
        // Get all the exercises.
7598
        $exercises = $this->get_exercises();
7599
7600
        // Get all the links.
7601
        $links = $this->get_links();
7602
7603
        // Get all the student publications.
7604
        $works = $this->get_student_publications();
7605
7606
        // Get all the forums.
7607
        $forums = $this->get_forums(null, $course_code);
7608
7609
        // Get the final item form (see BT#11048) .
7610
        $finish = $this->getFinalItemForm();
7611
7612
        $headers = [
7613
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7614
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7615
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7616
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7617
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7618
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7619
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7620
        ];
7621
7622
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7623
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7624
        echo Display::tabs(
7625
            $headers,
7626
            [
7627
                $documents,
7628
                $exercises,
7629
                $links,
7630
                $works,
7631
                $forums,
7632
                $dir,
7633
                $finish,
7634
            ],
7635
            'resource_tab'
7636
        );
7637
7638
        return true;
7639
    }
7640
7641
    /**
7642
     * Returns the extension of a document.
7643
     *
7644
     * @param string $filename
7645
     *
7646
     * @return string Extension (part after the last dot)
7647
     */
7648
    public function get_extension($filename)
7649
    {
7650
        $explode = explode('.', $filename);
7651
7652
        return $explode[count($explode) - 1];
7653
    }
7654
7655
    /**
7656
     * Displays a document by id.
7657
     *
7658
     * @param int  $id
7659
     * @param bool $show_title
7660
     * @param bool $iframe
7661
     * @param bool $edit_link
7662
     *
7663
     * @return string
7664
     */
7665
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7666
    {
7667
        $_course = api_get_course_info();
7668
        $course_id = api_get_course_int_id();
7669
        $id = (int) $id;
7670
        $return = '';
7671
        $table = Database::get_course_table(TABLE_DOCUMENT);
7672
        $sql_doc = "SELECT * FROM $table
7673
                    WHERE c_id = $course_id AND iid = $id";
7674
        $res_doc = Database::query($sql_doc);
7675
        $row_doc = Database::fetch_array($res_doc);
7676
7677
        // TODO: Add a path filter.
7678
        if ($iframe) {
7679
            $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>';
7680
        } else {
7681
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7682
        }
7683
7684
        return $return;
7685
    }
7686
7687
    /**
7688
     * Return HTML form to add/edit a quiz.
7689
     *
7690
     * @param string $action     Action (add/edit)
7691
     * @param int    $id         Item ID if already exists
7692
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7693
     *
7694
     * @throws Exception
7695
     *
7696
     * @return string HTML form
7697
     */
7698
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7699
    {
7700
        $course_id = api_get_course_int_id();
7701
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7702
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7703
7704
        if ($id != 0 && is_array($extra_info)) {
7705
            $item_title = $extra_info['title'];
7706
            $item_description = $extra_info['description'];
7707
        } elseif (is_numeric($extra_info)) {
7708
            $sql = "SELECT title, description
7709
                    FROM $tbl_quiz
7710
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7711
7712
            $result = Database::query($sql);
7713
            $row = Database::fetch_array($result);
7714
            $item_title = $row['title'];
7715
            $item_description = $row['description'];
7716
        } else {
7717
            $item_title = '';
7718
            $item_description = '';
7719
        }
7720
        $item_title = Security::remove_XSS($item_title);
7721
        $item_description = Security::remove_XSS($item_description);
7722
7723
        if ($id != 0 && is_array($extra_info)) {
7724
            $parent = $extra_info['parent_item_id'];
7725
        } else {
7726
            $parent = 0;
7727
        }
7728
7729
        $sql = "SELECT * FROM $tbl_lp_item 
7730
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7731
7732
        $result = Database::query($sql);
7733
        $arrLP = [];
7734
        while ($row = Database::fetch_array($result)) {
7735
            $arrLP[] = [
7736
                'id' => $row['iid'],
7737
                'item_type' => $row['item_type'],
7738
                'title' => $row['title'],
7739
                'path' => $row['path'],
7740
                'description' => $row['description'],
7741
                'parent_item_id' => $row['parent_item_id'],
7742
                'previous_item_id' => $row['previous_item_id'],
7743
                'next_item_id' => $row['next_item_id'],
7744
                'display_order' => $row['display_order'],
7745
                'max_score' => $row['max_score'],
7746
                'min_score' => $row['min_score'],
7747
                'mastery_score' => $row['mastery_score'],
7748
                'prerequisite' => $row['prerequisite'],
7749
                'max_time_allowed' => $row['max_time_allowed'],
7750
            ];
7751
        }
7752
7753
        $this->tree_array($arrLP);
7754
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7755
        unset($this->arrMenu);
7756
7757
        $form = new FormValidator(
7758
            'quiz_form',
7759
            'POST',
7760
            $this->getCurrentBuildingModeURL()
7761
        );
7762
        $defaults = [];
7763
7764
        if ($action == 'add') {
7765
            $legend = get_lang('CreateTheExercise');
7766
        } elseif ($action == 'move') {
7767
            $legend = get_lang('MoveTheCurrentExercise');
7768
        } else {
7769
            $legend = get_lang('EditCurrentExecice');
7770
        }
7771
7772
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7773
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7774
        }
7775
7776
        $form->addHeader($legend);
7777
7778
        if ($action != 'move') {
7779
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle']);
7780
            $defaults['title'] = $item_title;
7781
        }
7782
7783
        // Select for Parent item, root or chapter
7784
        $selectParent = $form->addSelect(
7785
            'parent',
7786
            get_lang('Parent'),
7787
            [],
7788
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7789
        );
7790
        $selectParent->addOption($this->name, 0);
7791
7792
        $arrHide = [
7793
            $id,
7794
        ];
7795
        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...
7796
            if ($action != 'add') {
7797
                if (
7798
                    ($arrLP[$i]['item_type'] == 'dir') &&
7799
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7800
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7801
                ) {
7802
                    $selectParent->addOption(
7803
                        $arrLP[$i]['title'],
7804
                        $arrLP[$i]['id'],
7805
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7806
                    );
7807
7808
                    if ($parent == $arrLP[$i]['id']) {
7809
                        $selectParent->setSelected($arrLP[$i]['id']);
7810
                    }
7811
                } else {
7812
                    $arrHide[] = $arrLP[$i]['id'];
7813
                }
7814
            } else {
7815
                if ($arrLP[$i]['item_type'] == 'dir') {
7816
                    $selectParent->addOption(
7817
                        $arrLP[$i]['title'],
7818
                        $arrLP[$i]['id'],
7819
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7820
                    );
7821
7822
                    if ($parent == $arrLP[$i]['id']) {
7823
                        $selectParent->setSelected($arrLP[$i]['id']);
7824
                    }
7825
                }
7826
            }
7827
        }
7828
        if (is_array($arrLP)) {
7829
            reset($arrLP);
7830
        }
7831
7832
        $selectPrevious = $form->addSelect(
7833
            'previous',
7834
            get_lang('Position'),
7835
            [],
7836
            ['id' => 'previous']
7837
        );
7838
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7839
7840
        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...
7841
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7842
                $arrLP[$i]['id'] != $id
7843
            ) {
7844
                $selectPrevious->addOption(
7845
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7846
                    $arrLP[$i]['id']
7847
                );
7848
7849
                if (is_array($extra_info)) {
7850
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7851
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7852
                    }
7853
                } elseif ($action == 'add') {
7854
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7855
                }
7856
            }
7857
        }
7858
7859
        if ($action != 'move') {
7860
            $arrHide = [];
7861
            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...
7862
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7863
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7864
                }
7865
            }
7866
        }
7867
7868
        if ($action == 'add') {
7869
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7870
        } else {
7871
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7872
        }
7873
7874
        if ($action == 'move') {
7875
            $form->addHidden('title', $item_title);
7876
            $form->addHidden('description', $item_description);
7877
        }
7878
7879
        if (is_numeric($extra_info)) {
7880
            $form->addHidden('path', $extra_info);
7881
        } elseif (is_array($extra_info)) {
7882
            $form->addHidden('path', $extra_info['path']);
7883
        }
7884
7885
        $form->addHidden('type', TOOL_QUIZ);
7886
        $form->addHidden('post_time', time());
7887
        $form->setDefaults($defaults);
7888
7889
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7890
    }
7891
7892
    /**
7893
     * Addition of Hotpotatoes tests.
7894
     *
7895
     * @param string $action
7896
     * @param int    $id         Internal ID of the item
7897
     * @param string $extra_info
7898
     *
7899
     * @return string HTML structure to display the hotpotatoes addition formular
7900
     */
7901
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7902
    {
7903
        $course_id = api_get_course_int_id();
7904
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7905
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7906
7907
        if ($id != 0 && is_array($extra_info)) {
7908
            $item_title = stripslashes($extra_info['title']);
7909
            $item_description = stripslashes($extra_info['description']);
7910
        } elseif (is_numeric($extra_info)) {
7911
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7912
7913
            $sql = "SELECT * FROM ".$TBL_DOCUMENT."
7914
                    WHERE
7915
                        c_id = ".$course_id." AND
7916
                        path LIKE '".$uploadPath."/%/%htm%' AND
7917
                        iid = ".(int) $extra_info."
7918
                    ORDER BY iid ASC";
7919
7920
            $res_hot = Database::query($sql);
7921
            $row = Database::fetch_array($res_hot);
7922
7923
            $item_title = $row['title'];
7924
            $item_description = $row['description'];
7925
7926
            if (!empty($row['comment'])) {
7927
                $item_title = $row['comment'];
7928
            }
7929
        } else {
7930
            $item_title = '';
7931
            $item_description = '';
7932
        }
7933
7934
        if ($id != 0 && is_array($extra_info)) {
7935
            $parent = $extra_info['parent_item_id'];
7936
        } else {
7937
            $parent = 0;
7938
        }
7939
7940
        $sql = "SELECT * FROM $tbl_lp_item
7941
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
7942
        $result = Database::query($sql);
7943
        $arrLP = [];
7944
        while ($row = Database::fetch_array($result)) {
7945
            $arrLP[] = [
7946
                'id' => $row['id'],
7947
                'item_type' => $row['item_type'],
7948
                'title' => $row['title'],
7949
                'path' => $row['path'],
7950
                'description' => $row['description'],
7951
                'parent_item_id' => $row['parent_item_id'],
7952
                'previous_item_id' => $row['previous_item_id'],
7953
                'next_item_id' => $row['next_item_id'],
7954
                'display_order' => $row['display_order'],
7955
                'max_score' => $row['max_score'],
7956
                'min_score' => $row['min_score'],
7957
                'mastery_score' => $row['mastery_score'],
7958
                'prerequisite' => $row['prerequisite'],
7959
                'max_time_allowed' => $row['max_time_allowed'],
7960
            ];
7961
        }
7962
7963
        $legend = '<legend>';
7964
        if ($action == 'add') {
7965
            $legend .= get_lang('CreateTheExercise');
7966
        } elseif ($action == 'move') {
7967
            $legend .= get_lang('MoveTheCurrentExercise');
7968
        } else {
7969
            $legend .= get_lang('EditCurrentExecice');
7970
        }
7971
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7972
            $legend .= Display:: return_message(
7973
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7974
            );
7975
        }
7976
        $legend .= '</legend>';
7977
7978
        $return = '<form method="POST">';
7979
        $return .= $legend;
7980
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7981
        $return .= '<tr>';
7982
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7983
        $return .= '<td class="input">';
7984
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7985
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7986
        $arrHide = [
7987
            $id,
7988
        ];
7989
7990
        if (count($arrLP) > 0) {
7991
            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...
7992
                if ($action != 'add') {
7993
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7994
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7995
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7996
                    ) {
7997
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7998
                    } else {
7999
                        $arrHide[] = $arrLP[$i]['id'];
8000
                    }
8001
                } else {
8002
                    if ($arrLP[$i]['item_type'] == 'dir') {
8003
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
8004
                    }
8005
                }
8006
            }
8007
            reset($arrLP);
8008
        }
8009
8010
        $return .= '</select>';
8011
        $return .= '</td>';
8012
        $return .= '</tr>';
8013
        $return .= '<tr>';
8014
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
8015
        $return .= '<td class="input">';
8016
        $return .= '<select id="previous" name="previous" size="1">';
8017
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
8018
8019
        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...
8020
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8021
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8022
                    $selected = 'selected="selected" ';
8023
                } elseif ($action == 'add') {
8024
                    $selected = 'selected="selected" ';
8025
                } else {
8026
                    $selected = '';
8027
                }
8028
8029
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
8030
            }
8031
        }
8032
8033
        $return .= '</select>';
8034
        $return .= '</td>';
8035
        $return .= '</tr>';
8036
8037
        if ($action != 'move') {
8038
            $return .= '<tr>';
8039
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
8040
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
8041
            $return .= '</tr>';
8042
            $id_prerequisite = 0;
8043
            if (is_array($arrLP) && count($arrLP) > 0) {
8044
                foreach ($arrLP as $key => $value) {
8045
                    if ($value['id'] == $id) {
8046
                        $id_prerequisite = $value['prerequisite'];
8047
                        break;
8048
                    }
8049
                }
8050
8051
                $arrHide = [];
8052
                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...
8053
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8054
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8055
                    }
8056
                }
8057
            }
8058
        }
8059
8060
        $return .= '<tr>';
8061
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
8062
            get_lang('SaveHotpotatoes').'</button></td>';
8063
        $return .= '</tr>';
8064
        $return .= '</table>';
8065
8066
        if ($action == 'move') {
8067
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
8068
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
8069
        }
8070
8071
        if (is_numeric($extra_info)) {
8072
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
8073
        } elseif (is_array($extra_info)) {
8074
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
8075
        }
8076
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
8077
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
8078
        $return .= '</form>';
8079
8080
        return $return;
8081
    }
8082
8083
    /**
8084
     * Return the form to display the forum edit/add option.
8085
     *
8086
     * @param string $action
8087
     * @param int    $id         ID of the lp_item if already exists
8088
     * @param string $extra_info
8089
     *
8090
     * @throws Exception
8091
     *
8092
     * @return string HTML form
8093
     */
8094
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
8095
    {
8096
        $course_id = api_get_course_int_id();
8097
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8098
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
8099
8100
        if ($id != 0 && is_array($extra_info)) {
8101
            $item_title = stripslashes($extra_info['title']);
8102
        } elseif (is_numeric($extra_info)) {
8103
            $sql = "SELECT forum_title as title, forum_comment as comment
8104
                    FROM ".$tbl_forum."
8105
                    WHERE c_id = ".$course_id." AND forum_id = ".$extra_info;
8106
8107
            $result = Database::query($sql);
8108
            $row = Database::fetch_array($result);
8109
8110
            $item_title = $row['title'];
8111
            $item_description = $row['comment'];
8112
        } else {
8113
            $item_title = '';
8114
            $item_description = '';
8115
        }
8116
8117
        if ($id != 0 && is_array($extra_info)) {
8118
            $parent = $extra_info['parent_item_id'];
8119
        } else {
8120
            $parent = 0;
8121
        }
8122
8123
        $sql = "SELECT * FROM $tbl_lp_item
8124
                WHERE
8125
                    c_id = $course_id AND
8126
                    lp_id = ".$this->lp_id;
8127
        $result = Database::query($sql);
8128
        $arrLP = [];
8129
        while ($row = Database::fetch_array($result)) {
8130
            $arrLP[] = [
8131
                'id' => $row['iid'],
8132
                'item_type' => $row['item_type'],
8133
                'title' => $row['title'],
8134
                'path' => $row['path'],
8135
                'description' => $row['description'],
8136
                'parent_item_id' => $row['parent_item_id'],
8137
                'previous_item_id' => $row['previous_item_id'],
8138
                'next_item_id' => $row['next_item_id'],
8139
                'display_order' => $row['display_order'],
8140
                'max_score' => $row['max_score'],
8141
                'min_score' => $row['min_score'],
8142
                'mastery_score' => $row['mastery_score'],
8143
                'prerequisite' => $row['prerequisite'],
8144
            ];
8145
        }
8146
8147
        $this->tree_array($arrLP);
8148
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8149
        unset($this->arrMenu);
8150
8151
        if ($action == 'add') {
8152
            $legend = get_lang('CreateTheForum');
8153
        } elseif ($action == 'move') {
8154
            $legend = get_lang('MoveTheCurrentForum');
8155
        } else {
8156
            $legend = get_lang('EditCurrentForum');
8157
        }
8158
8159
        $form = new FormValidator(
8160
            'forum_form',
8161
            'POST',
8162
            $this->getCurrentBuildingModeURL()
8163
        );
8164
        $defaults = [];
8165
8166
        $form->addHeader($legend);
8167
8168
        if ($action != 'move') {
8169
            $form->addText(
8170
                'title',
8171
                get_lang('Title'),
8172
                true,
8173
                ['id' => 'idTitle', 'class' => 'learnpath_item_form']
8174
            );
8175
            $defaults['title'] = $item_title;
8176
        }
8177
8178
        $selectParent = $form->addSelect(
8179
            'parent',
8180
            get_lang('Parent'),
8181
            [],
8182
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8183
        );
8184
        $selectParent->addOption($this->name, 0);
8185
        $arrHide = [
8186
            $id,
8187
        ];
8188
        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...
8189
            if ($action != 'add') {
8190
                if ($arrLP[$i]['item_type'] == 'dir' &&
8191
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8192
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8193
                ) {
8194
                    $selectParent->addOption(
8195
                        $arrLP[$i]['title'],
8196
                        $arrLP[$i]['id'],
8197
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8198
                    );
8199
8200
                    if ($parent == $arrLP[$i]['id']) {
8201
                        $selectParent->setSelected($arrLP[$i]['id']);
8202
                    }
8203
                } else {
8204
                    $arrHide[] = $arrLP[$i]['id'];
8205
                }
8206
            } else {
8207
                if ($arrLP[$i]['item_type'] == 'dir') {
8208
                    $selectParent->addOption(
8209
                        $arrLP[$i]['title'],
8210
                        $arrLP[$i]['id'],
8211
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8212
                    );
8213
8214
                    if ($parent == $arrLP[$i]['id']) {
8215
                        $selectParent->setSelected($arrLP[$i]['id']);
8216
                    }
8217
                }
8218
            }
8219
        }
8220
8221
        if (is_array($arrLP)) {
8222
            reset($arrLP);
8223
        }
8224
8225
        $selectPrevious = $form->addSelect(
8226
            'previous',
8227
            get_lang('Position'),
8228
            [],
8229
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8230
        );
8231
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8232
8233
        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...
8234
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8235
                $arrLP[$i]['id'] != $id
8236
            ) {
8237
                $selectPrevious->addOption(
8238
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8239
                    $arrLP[$i]['id']
8240
                );
8241
8242
                if (isset($extra_info['previous_item_id']) &&
8243
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8244
                ) {
8245
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8246
                } elseif ($action == 'add') {
8247
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8248
                }
8249
            }
8250
        }
8251
8252
        if ($action != 'move') {
8253
            $id_prerequisite = 0;
8254
            if (is_array($arrLP)) {
8255
                foreach ($arrLP as $key => $value) {
8256
                    if ($value['id'] == $id) {
8257
                        $id_prerequisite = $value['prerequisite'];
8258
                        break;
8259
                    }
8260
                }
8261
            }
8262
8263
            $arrHide = [];
8264
            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...
8265
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8266
                    if (isset($extra_info['previous_item_id']) &&
8267
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8268
                    ) {
8269
                        $s_selected_position = $arrLP[$i]['id'];
8270
                    } elseif ($action == 'add') {
8271
                        $s_selected_position = 0;
8272
                    }
8273
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8274
                }
8275
            }
8276
        }
8277
8278
        if ($action == 'add') {
8279
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8280
        } else {
8281
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8282
        }
8283
8284
        if ($action == 'move') {
8285
            $form->addHidden('title', $item_title);
8286
            $form->addHidden('description', $item_description);
8287
        }
8288
8289
        if (is_numeric($extra_info)) {
8290
            $form->addHidden('path', $extra_info);
8291
        } elseif (is_array($extra_info)) {
8292
            $form->addHidden('path', $extra_info['path']);
8293
        }
8294
        $form->addHidden('type', TOOL_FORUM);
8295
        $form->addHidden('post_time', time());
8296
        $form->setDefaults($defaults);
8297
8298
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8299
    }
8300
8301
    /**
8302
     * Return HTML form to add/edit forum threads.
8303
     *
8304
     * @param string $action
8305
     * @param int    $id         Item ID if already exists in learning path
8306
     * @param string $extra_info
8307
     *
8308
     * @throws Exception
8309
     *
8310
     * @return string HTML form
8311
     */
8312
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8313
    {
8314
        $course_id = api_get_course_int_id();
8315
        if (empty($course_id)) {
8316
            return null;
8317
        }
8318
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8319
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8320
8321
        if ($id != 0 && is_array($extra_info)) {
8322
            $item_title = stripslashes($extra_info['title']);
8323
        } elseif (is_numeric($extra_info)) {
8324
            $sql = "SELECT thread_title as title FROM $tbl_forum
8325
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8326
8327
            $result = Database::query($sql);
8328
            $row = Database::fetch_array($result);
8329
8330
            $item_title = $row['title'];
8331
            $item_description = '';
8332
        } else {
8333
            $item_title = '';
8334
            $item_description = '';
8335
        }
8336
8337
        if ($id != 0 && is_array($extra_info)) {
8338
            $parent = $extra_info['parent_item_id'];
8339
        } else {
8340
            $parent = 0;
8341
        }
8342
8343
        $sql = "SELECT * FROM $tbl_lp_item
8344
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8345
        $result = Database::query($sql);
8346
8347
        $arrLP = [];
8348
        while ($row = Database::fetch_array($result)) {
8349
            $arrLP[] = [
8350
                'id' => $row['iid'],
8351
                'item_type' => $row['item_type'],
8352
                'title' => $row['title'],
8353
                'path' => $row['path'],
8354
                'description' => $row['description'],
8355
                'parent_item_id' => $row['parent_item_id'],
8356
                'previous_item_id' => $row['previous_item_id'],
8357
                'next_item_id' => $row['next_item_id'],
8358
                'display_order' => $row['display_order'],
8359
                'max_score' => $row['max_score'],
8360
                'min_score' => $row['min_score'],
8361
                'mastery_score' => $row['mastery_score'],
8362
                'prerequisite' => $row['prerequisite'],
8363
            ];
8364
        }
8365
8366
        $this->tree_array($arrLP);
8367
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8368
        unset($this->arrMenu);
8369
8370
        $form = new FormValidator(
8371
            'thread_form',
8372
            'POST',
8373
            $this->getCurrentBuildingModeURL()
8374
        );
8375
        $defaults = [];
8376
8377
        if ($action == 'add') {
8378
            $legend = get_lang('CreateTheForum');
8379
        } elseif ($action == 'move') {
8380
            $legend = get_lang('MoveTheCurrentForum');
8381
        } else {
8382
            $legend = get_lang('EditCurrentForum');
8383
        }
8384
8385
        $form->addHeader($legend);
8386
        $selectParent = $form->addSelect(
8387
            'parent',
8388
            get_lang('Parent'),
8389
            [],
8390
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8391
        );
8392
        $selectParent->addOption($this->name, 0);
8393
8394
        $arrHide = [
8395
            $id,
8396
        ];
8397
8398
        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...
8399
            if ($action != 'add') {
8400
                if (
8401
                    ($arrLP[$i]['item_type'] == 'dir') &&
8402
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8403
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8404
                ) {
8405
                    $selectParent->addOption(
8406
                        $arrLP[$i]['title'],
8407
                        $arrLP[$i]['id'],
8408
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8409
                    );
8410
8411
                    if ($parent == $arrLP[$i]['id']) {
8412
                        $selectParent->setSelected($arrLP[$i]['id']);
8413
                    }
8414
                } else {
8415
                    $arrHide[] = $arrLP[$i]['id'];
8416
                }
8417
            } else {
8418
                if ($arrLP[$i]['item_type'] == 'dir') {
8419
                    $selectParent->addOption(
8420
                        $arrLP[$i]['title'],
8421
                        $arrLP[$i]['id'],
8422
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8423
                    );
8424
8425
                    if ($parent == $arrLP[$i]['id']) {
8426
                        $selectParent->setSelected($arrLP[$i]['id']);
8427
                    }
8428
                }
8429
            }
8430
        }
8431
8432
        if ($arrLP != null) {
8433
            reset($arrLP);
8434
        }
8435
8436
        $selectPrevious = $form->addSelect(
8437
            'previous',
8438
            get_lang('Position'),
8439
            [],
8440
            ['id' => 'previous']
8441
        );
8442
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8443
8444
        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...
8445
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8446
                $selectPrevious->addOption(
8447
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8448
                    $arrLP[$i]['id']
8449
                );
8450
8451
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8452
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8453
                } elseif ($action == 'add') {
8454
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8455
                }
8456
            }
8457
        }
8458
8459
        if ($action != 'move') {
8460
            $form->addText(
8461
                'title',
8462
                get_lang('Title'),
8463
                true,
8464
                ['id' => 'idTitle']
8465
            );
8466
            $defaults['title'] = $item_title;
8467
8468
            $id_prerequisite = 0;
8469
            if ($arrLP != null) {
8470
                foreach ($arrLP as $key => $value) {
8471
                    if ($value['id'] == $id) {
8472
                        $id_prerequisite = $value['prerequisite'];
8473
                        break;
8474
                    }
8475
                }
8476
            }
8477
8478
            $arrHide = [];
8479
            $s_selected_position = 0;
8480
            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...
8481
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8482
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8483
                        $s_selected_position = $arrLP[$i]['id'];
8484
                    } elseif ($action == 'add') {
8485
                        $s_selected_position = 0;
8486
                    }
8487
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8488
                }
8489
            }
8490
8491
            $selectPrerequisites = $form->addSelect(
8492
                'prerequisites',
8493
                get_lang('LearnpathPrerequisites'),
8494
                [],
8495
                ['id' => 'prerequisites']
8496
            );
8497
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8498
8499
            foreach ($arrHide as $key => $value) {
8500
                $selectPrerequisites->addOption($value['value'], $key);
8501
8502
                if ($key == $s_selected_position && $action == 'add') {
8503
                    $selectPrerequisites->setSelected($key);
8504
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8505
                    $selectPrerequisites->setSelected($key);
8506
                }
8507
            }
8508
        }
8509
8510
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8511
8512
        if ($action == 'move') {
8513
            $form->addHidden('title', $item_title);
8514
            $form->addHidden('description', $item_description);
8515
        }
8516
8517
        if (is_numeric($extra_info)) {
8518
            $form->addHidden('path', $extra_info);
8519
        } elseif (is_array($extra_info)) {
8520
            $form->addHidden('path', $extra_info['path']);
8521
        }
8522
8523
        $form->addHidden('type', TOOL_THREAD);
8524
        $form->addHidden('post_time', time());
8525
        $form->setDefaults($defaults);
8526
8527
        return $form->returnForm();
8528
    }
8529
8530
    /**
8531
     * Return the HTML form to display an item (generally a dir item).
8532
     *
8533
     * @param string $item_type
8534
     * @param string $title
8535
     * @param string $action
8536
     * @param int    $id
8537
     * @param string $extra_info
8538
     *
8539
     * @throws Exception
8540
     * @throws HTML_QuickForm_Error
8541
     *
8542
     * @return string HTML form
8543
     */
8544
    public function display_item_form(
8545
        $item_type,
8546
        $title = '',
8547
        $action = 'add_item',
8548
        $id = 0,
8549
        $extra_info = 'new'
8550
    ) {
8551
        $_course = api_get_course_info();
8552
8553
        global $charset;
8554
8555
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8556
        $item_title = '';
8557
        $item_description = '';
8558
        $item_path_fck = '';
8559
8560
        if ($id != 0 && is_array($extra_info)) {
8561
            $item_title = $extra_info['title'];
8562
            $item_description = $extra_info['description'];
8563
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8564
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8565
        }
8566
        $parent = 0;
8567
        if ($id != 0 && is_array($extra_info)) {
8568
            $parent = $extra_info['parent_item_id'];
8569
        }
8570
8571
        $id = (int) $id;
8572
        $sql = "SELECT * FROM $tbl_lp_item
8573
                WHERE
8574
                    lp_id = ".$this->lp_id." AND
8575
                    iid != $id";
8576
8577
        if ($item_type == 'dir') {
8578
            $sql .= " AND parent_item_id = 0";
8579
        }
8580
8581
        $result = Database::query($sql);
8582
        $arrLP = [];
8583
        while ($row = Database::fetch_array($result)) {
8584
            $arrLP[] = [
8585
                'id' => $row['iid'],
8586
                'item_type' => $row['item_type'],
8587
                'title' => $row['title'],
8588
                'path' => $row['path'],
8589
                'description' => $row['description'],
8590
                'parent_item_id' => $row['parent_item_id'],
8591
                'previous_item_id' => $row['previous_item_id'],
8592
                'next_item_id' => $row['next_item_id'],
8593
                'max_score' => $row['max_score'],
8594
                'min_score' => $row['min_score'],
8595
                'mastery_score' => $row['mastery_score'],
8596
                'prerequisite' => $row['prerequisite'],
8597
                'display_order' => $row['display_order'],
8598
            ];
8599
        }
8600
8601
        $this->tree_array($arrLP);
8602
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8603
        unset($this->arrMenu);
8604
8605
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8606
8607
        $form = new FormValidator('form', 'POST', $url);
8608
        $defaults['title'] = api_html_entity_decode(
8609
            $item_title,
8610
            ENT_QUOTES,
8611
            $charset
8612
        );
8613
        $defaults['description'] = $item_description;
8614
8615
        $form->addHeader($title);
8616
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8617
        $arrHide[0]['padding'] = 20;
8618
        $charset = api_get_system_encoding();
8619
        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...
8620
            if ($action != 'add') {
8621
                if ($arrLP[$i]['item_type'] == 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8622
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8623
                ) {
8624
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8625
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8626
                    if ($parent == $arrLP[$i]['id']) {
8627
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8628
                    }
8629
                }
8630
            } else {
8631
                if ($arrLP[$i]['item_type'] == 'dir') {
8632
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8633
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8634
                    if ($parent == $arrLP[$i]['id']) {
8635
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8636
                    }
8637
                }
8638
            }
8639
        }
8640
8641
        if ($action != 'move') {
8642
            $form->addElement('text', 'title', get_lang('Title'));
8643
            $form->applyFilter('title', 'html_filter');
8644
            $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
8645
        } else {
8646
            $form->addElement('hidden', 'title');
8647
        }
8648
8649
        $parentSelect = $form->addElement(
8650
            'select',
8651
            'parent',
8652
            get_lang('Parent'),
8653
            '',
8654
            [
8655
                'id' => 'idParent',
8656
                'onchange' => "javascript: load_cbo(this.value);",
8657
            ]
8658
        );
8659
8660
        foreach ($arrHide as $key => $value) {
8661
            $parentSelect->addOption(
8662
                $value['value'],
8663
                $key,
8664
                'style="padding-left:'.$value['padding'].'px;"'
8665
            );
8666
            $lastPosition = $key;
8667
        }
8668
8669
        if (!empty($s_selected_parent)) {
8670
            $parentSelect->setSelected($s_selected_parent);
8671
        }
8672
8673
        if (is_array($arrLP)) {
8674
            reset($arrLP);
8675
        }
8676
        $arrHide = [];
8677
        // POSITION
8678
        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...
8679
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8680
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8681
                //this is the same!
8682
                if (isset($extra_info['previous_item_id']) &&
8683
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8684
                ) {
8685
                    $s_selected_position = $arrLP[$i]['id'];
8686
                } elseif ($action == 'add') {
8687
                    $s_selected_position = $arrLP[$i]['id'];
8688
                }
8689
8690
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8691
            }
8692
        }
8693
8694
        $position = $form->addElement(
8695
            'select',
8696
            'previous',
8697
            get_lang('Position'),
8698
            '',
8699
            ['id' => 'previous']
8700
        );
8701
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8702
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8703
8704
        $lastPosition = null;
8705
        foreach ($arrHide as $key => $value) {
8706
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8707
            $lastPosition = $key;
8708
        }
8709
8710
        if (!empty($s_selected_position)) {
8711
            $position->setSelected($s_selected_position);
8712
        }
8713
8714
        // When new chapter add at the end
8715
        if ($action == 'add_item') {
8716
            $position->setSelected($lastPosition);
8717
        }
8718
8719
        if (is_array($arrLP)) {
8720
            reset($arrLP);
8721
        }
8722
8723
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8724
8725
        //fix in order to use the tab
8726
        if ($item_type == 'dir') {
8727
            $form->addElement('hidden', 'type', 'dir');
8728
        }
8729
8730
        $extension = null;
8731
        if (!empty($item_path)) {
8732
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8733
        }
8734
8735
        //assets can't be modified
8736
        //$item_type == 'asset' ||
8737
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8738
            if ($item_type == 'sco') {
8739
                $form->addElement(
8740
                    'html',
8741
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8742
                );
8743
            }
8744
            $renderer = $form->defaultRenderer();
8745
            $renderer->setElementTemplate(
8746
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8747
                'content_lp'
8748
            );
8749
8750
            $relative_prefix = '';
8751
8752
            $editor_config = [
8753
                'ToolbarSet' => 'LearningPathDocuments',
8754
                'Width' => '100%',
8755
                'Height' => '500',
8756
                'FullPage' => true,
8757
                'CreateDocumentDir' => $relative_prefix,
8758
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8759
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8760
            ];
8761
8762
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8763
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8764
            $defaults['content_lp'] = file_get_contents($content_path);
8765
        }
8766
8767
        if (!empty($id)) {
8768
            $form->addHidden('id', $id);
8769
        }
8770
8771
        $form->addElement('hidden', 'type', $item_type);
8772
        $form->addElement('hidden', 'post_time', time());
8773
        $form->setDefaults($defaults);
8774
8775
        return $form->returnForm();
8776
    }
8777
8778
    /**
8779
     * @return string
8780
     */
8781
    public function getCurrentBuildingModeURL()
8782
    {
8783
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8784
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8785
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8786
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8787
8788
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8789
8790
        return $currentUrl;
8791
    }
8792
8793
    /**
8794
     * Returns the form to update or create a document.
8795
     *
8796
     * @param string $action     (add/edit)
8797
     * @param int    $id         ID of the lp_item (if already exists)
8798
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8799
     *
8800
     * @throws Exception
8801
     * @throws HTML_QuickForm_Error
8802
     *
8803
     * @return string HTML form
8804
     */
8805
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8806
    {
8807
        $course_id = api_get_course_int_id();
8808
        $_course = api_get_course_info();
8809
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8810
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8811
8812
        $no_display_edit_textarea = false;
8813
        $item_description = '';
8814
        //If action==edit document
8815
        //We don't display the document form if it's not an editable document (html or txt file)
8816
        if ($action === 'edit') {
8817
            if (is_array($extra_info)) {
8818
                $path_parts = pathinfo($extra_info['dir']);
8819
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8820
                    $no_display_edit_textarea = true;
8821
                }
8822
            }
8823
        }
8824
        $no_display_add = false;
8825
8826
        // If action==add an existing document
8827
        // We don't display the document form if it's not an editable document (html or txt file).
8828
        if ($action === 'add') {
8829
            if (is_numeric($extra_info)) {
8830
                $extra_info = (int) $extra_info;
8831
                $sql_doc = "SELECT path FROM $tbl_doc 
8832
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8833
                $result = Database::query($sql_doc);
8834
                $path_file = Database::result($result, 0, 0);
8835
                $path_parts = pathinfo($path_file);
8836
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8837
                    $no_display_add = true;
8838
                }
8839
            }
8840
        }
8841
        if ($id != 0 && is_array($extra_info)) {
8842
            $item_title = stripslashes($extra_info['title']);
8843
            $item_description = stripslashes($extra_info['description']);
8844
            if (empty($item_title)) {
8845
                $path_parts = pathinfo($extra_info['path']);
8846
                $item_title = stripslashes($path_parts['filename']);
8847
            }
8848
        } elseif (is_numeric($extra_info)) {
8849
            $sql = "SELECT path, title FROM $tbl_doc
8850
                    WHERE
8851
                        c_id = ".$course_id." AND
8852
                        iid = ".intval($extra_info);
8853
            $result = Database::query($sql);
8854
            $row = Database::fetch_array($result);
8855
            $item_title = $row['title'];
8856
            $item_title = str_replace('_', ' ', $item_title);
8857
            if (empty($item_title)) {
8858
                $path_parts = pathinfo($row['path']);
8859
                $item_title = stripslashes($path_parts['filename']);
8860
            }
8861
        } else {
8862
            $item_title = '';
8863
            $item_description = '';
8864
        }
8865
        $return = '<legend>';
8866
        $parent = 0;
8867
        if ($id != 0 && is_array($extra_info)) {
8868
            $parent = $extra_info['parent_item_id'];
8869
        }
8870
8871
        $sql = "SELECT * FROM $tbl_lp_item
8872
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8873
        $result = Database::query($sql);
8874
        $arrLP = [];
8875
8876
        while ($row = Database::fetch_array($result)) {
8877
            $arrLP[] = [
8878
                'id' => $row['iid'],
8879
                'item_type' => $row['item_type'],
8880
                'title' => $row['title'],
8881
                'path' => $row['path'],
8882
                'description' => $row['description'],
8883
                'parent_item_id' => $row['parent_item_id'],
8884
                'previous_item_id' => $row['previous_item_id'],
8885
                'next_item_id' => $row['next_item_id'],
8886
                'display_order' => $row['display_order'],
8887
                'max_score' => $row['max_score'],
8888
                'min_score' => $row['min_score'],
8889
                'mastery_score' => $row['mastery_score'],
8890
                'prerequisite' => $row['prerequisite'],
8891
            ];
8892
        }
8893
8894
        $this->tree_array($arrLP);
8895
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8896
        unset($this->arrMenu);
8897
8898
        if ($action == 'add') {
8899
            $return .= get_lang('CreateTheDocument');
8900
        } elseif ($action == 'move') {
8901
            $return .= get_lang('MoveTheCurrentDocument');
8902
        } else {
8903
            $return .= get_lang('EditTheCurrentDocument');
8904
        }
8905
        $return .= '</legend>';
8906
8907
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8908
            $return .= Display::return_message(
8909
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8910
                false
8911
            );
8912
        }
8913
        $form = new FormValidator(
8914
            'form',
8915
            'POST',
8916
            $this->getCurrentBuildingModeURL(),
8917
            '',
8918
            ['enctype' => 'multipart/form-data']
8919
        );
8920
        $defaults['title'] = Security::remove_XSS($item_title);
8921
        if (empty($item_title)) {
8922
            $defaults['title'] = Security::remove_XSS($item_title);
8923
        }
8924
        $defaults['description'] = $item_description;
8925
        $form->addElement('html', $return);
8926
8927
        if ($action != 'move') {
8928
            $data = $this->generate_lp_folder($_course);
8929
            if ($action != 'edit') {
8930
                $folders = DocumentManager::get_all_document_folders(
8931
                    $_course,
8932
                    0,
8933
                    true
8934
                );
8935
                DocumentManager::build_directory_selector(
8936
                    $folders,
8937
                    '',
8938
                    [],
8939
                    true,
8940
                    $form,
8941
                    'directory_parent_id'
8942
                );
8943
            }
8944
8945
            if (isset($data['id'])) {
8946
                $defaults['directory_parent_id'] = $data['id'];
8947
            }
8948
8949
            $form->addElement(
8950
                'text',
8951
                'title',
8952
                get_lang('Title'),
8953
                ['id' => 'idTitle', 'class' => 'col-md-4']
8954
            );
8955
            $form->applyFilter('title', 'html_filter');
8956
        }
8957
8958
        $arrHide[0]['value'] = $this->name;
8959
        $arrHide[0]['padding'] = 20;
8960
8961
        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...
8962
            if ($action != 'add') {
8963
                if ($arrLP[$i]['item_type'] == 'dir' &&
8964
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8965
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8966
                ) {
8967
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8968
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8969
                }
8970
            } else {
8971
                if ($arrLP[$i]['item_type'] == 'dir') {
8972
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8973
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8974
                }
8975
            }
8976
        }
8977
8978
        $parentSelect = $form->addSelect(
8979
            'parent',
8980
            get_lang('Parent'),
8981
            [],
8982
            [
8983
                'id' => 'idParent',
8984
                'onchange' => 'javascript: load_cbo(this.value);',
8985
            ]
8986
        );
8987
8988
        $my_count = 0;
8989
        foreach ($arrHide as $key => $value) {
8990
            if ($my_count != 0) {
8991
                // The LP name is also the first section and is not in the same charset like the other sections.
8992
                $value['value'] = Security::remove_XSS($value['value']);
8993
                $parentSelect->addOption(
8994
                    $value['value'],
8995
                    $key,
8996
                    'style="padding-left:'.$value['padding'].'px;"'
8997
                );
8998
            } else {
8999
                $value['value'] = Security::remove_XSS($value['value']);
9000
                $parentSelect->addOption(
9001
                    $value['value'],
9002
                    $key,
9003
                    'style="padding-left:'.$value['padding'].'px;"'
9004
                );
9005
            }
9006
            $my_count++;
9007
        }
9008
9009
        if (!empty($id)) {
9010
            $parentSelect->setSelected($parent);
9011
        } else {
9012
            $parent_item_id = Session::read('parent_item_id', 0);
9013
            $parentSelect->setSelected($parent_item_id);
9014
        }
9015
9016
        if (is_array($arrLP)) {
9017
            reset($arrLP);
9018
        }
9019
9020
        $arrHide = [];
9021
        // POSITION
9022
        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...
9023
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
9024
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
9025
            ) {
9026
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9027
            }
9028
        }
9029
9030
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
9031
9032
        $position = $form->addSelect(
9033
            'previous',
9034
            get_lang('Position'),
9035
            [],
9036
            ['id' => 'previous']
9037
        );
9038
9039
        $position->addOption(get_lang('FirstPosition'), 0);
9040
        foreach ($arrHide as $key => $value) {
9041
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9042
            $position->addOption(
9043
                $value['value'],
9044
                $key,
9045
                'style="padding-left:'.$padding.'px;"'
9046
            );
9047
        }
9048
9049
        $position->setSelected($selectedPosition);
9050
9051
        if (is_array($arrLP)) {
9052
            reset($arrLP);
9053
        }
9054
9055
        if ($action != 'move') {
9056
            $arrHide = [];
9057
            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...
9058
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9059
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9060
                ) {
9061
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9062
                }
9063
            }
9064
9065
            if (!$no_display_add) {
9066
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9067
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9068
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
9069
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
9070
                ) {
9071
                    if (isset($_POST['content'])) {
9072
                        $content = stripslashes($_POST['content']);
9073
                    } elseif (is_array($extra_info)) {
9074
                        //If it's an html document or a text file
9075
                        if (!$no_display_edit_textarea) {
9076
                            $content = $this->display_document(
9077
                                $extra_info['path'],
9078
                                false,
9079
                                false
9080
                            );
9081
                        }
9082
                    } elseif (is_numeric($extra_info)) {
9083
                        $content = $this->display_document(
9084
                            $extra_info,
9085
                            false,
9086
                            false
9087
                        );
9088
                    } else {
9089
                        $content = '';
9090
                    }
9091
9092
                    if (!$no_display_edit_textarea) {
9093
                        // We need to calculate here some specific settings for the online editor.
9094
                        // The calculated settings work for documents in the Documents tool
9095
                        // (on the root or in subfolders).
9096
                        // For documents in native scorm packages it is unclear whether the
9097
                        // online editor should be activated or not.
9098
9099
                        // A new document, it is in the root of the repository.
9100
                        $relative_path = '';
9101
                        $relative_prefix = '';
9102
                        if (is_array($extra_info) && $extra_info != 'new') {
9103
                            // The document already exists. Whe have to determine its relative path towards the repository root.
9104
                            $relative_path = explode('/', $extra_info['dir']);
9105
                            $cnt = count($relative_path) - 2;
9106
                            if ($cnt < 0) {
9107
                                $cnt = 0;
9108
                            }
9109
                            $relative_prefix = str_repeat('../', $cnt);
9110
                            $relative_path = array_slice($relative_path, 1, $cnt);
9111
                            $relative_path = implode('/', $relative_path);
9112
                            if (strlen($relative_path) > 0) {
9113
                                $relative_path = $relative_path.'/';
9114
                            }
9115
                        } else {
9116
                            $result = $this->generate_lp_folder($_course);
9117
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
9118
                            $relative_prefix = '../../';
9119
                        }
9120
9121
                        $editor_config = [
9122
                            'ToolbarSet' => 'LearningPathDocuments',
9123
                            'Width' => '100%',
9124
                            'Height' => '500',
9125
                            'FullPage' => true,
9126
                            'CreateDocumentDir' => $relative_prefix,
9127
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
9128
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
9129
                        ];
9130
9131
                        if ($_GET['action'] == 'add_item') {
9132
                            $class = 'add';
9133
                            $text = get_lang('LPCreateDocument');
9134
                        } else {
9135
                            if ($_GET['action'] == 'edit_item') {
9136
                                $class = 'save';
9137
                                $text = get_lang('SaveDocument');
9138
                            }
9139
                        }
9140
9141
                        $form->addButtonSave($text, 'submit_button');
9142
                        $renderer = $form->defaultRenderer();
9143
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
9144
                        $form->addElement('html', '<div class="editor-lp">');
9145
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
9146
                        $form->addElement('html', '</div>');
9147
                        $defaults['content_lp'] = $content;
9148
                    }
9149
                } elseif (is_numeric($extra_info)) {
9150
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9151
9152
                    $return = $this->display_document($extra_info, true, true, true);
9153
                    $form->addElement('html', $return);
9154
                }
9155
            }
9156
        }
9157
        if (isset($extra_info['item_type']) &&
9158
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9159
        ) {
9160
            $parentSelect->freeze();
9161
            $position->freeze();
9162
        }
9163
9164
        if ($action == 'move') {
9165
            $form->addElement('hidden', 'title', $item_title);
9166
            $form->addElement('hidden', 'description', $item_description);
9167
        }
9168
        if (is_numeric($extra_info)) {
9169
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9170
            $form->addElement('hidden', 'path', $extra_info);
9171
        } elseif (is_array($extra_info)) {
9172
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9173
            $form->addElement('hidden', 'path', $extra_info['path']);
9174
        }
9175
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9176
        $form->addElement('hidden', 'post_time', time());
9177
        $form->setDefaults($defaults);
9178
9179
        return $form->returnForm();
9180
    }
9181
9182
    /**
9183
     * Returns the form to update or create a read-out text.
9184
     *
9185
     * @param string $action     "add" or "edit"
9186
     * @param int    $id         ID of the lp_item (if already exists)
9187
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9188
     *
9189
     * @throws Exception
9190
     * @throws HTML_QuickForm_Error
9191
     *
9192
     * @return string HTML form
9193
     */
9194
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9195
    {
9196
        $course_id = api_get_course_int_id();
9197
        $_course = api_get_course_info();
9198
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9199
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9200
9201
        $no_display_edit_textarea = false;
9202
        $item_description = '';
9203
        //If action==edit document
9204
        //We don't display the document form if it's not an editable document (html or txt file)
9205
        if ($action == 'edit') {
9206
            if (is_array($extra_info)) {
9207
                $path_parts = pathinfo($extra_info['dir']);
9208
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9209
                    $no_display_edit_textarea = true;
9210
                }
9211
            }
9212
        }
9213
        $no_display_add = false;
9214
9215
        if ($id != 0 && is_array($extra_info)) {
9216
            $item_title = stripslashes($extra_info['title']);
9217
            $item_description = stripslashes($extra_info['description']);
9218
            $item_terms = stripslashes($extra_info['terms']);
9219
            if (empty($item_title)) {
9220
                $path_parts = pathinfo($extra_info['path']);
9221
                $item_title = stripslashes($path_parts['filename']);
9222
            }
9223
        } elseif (is_numeric($extra_info)) {
9224
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9225
            $result = Database::query($sql);
9226
            $row = Database::fetch_array($result);
9227
            $item_title = $row['title'];
9228
            $item_title = str_replace('_', ' ', $item_title);
9229
            if (empty($item_title)) {
9230
                $path_parts = pathinfo($row['path']);
9231
                $item_title = stripslashes($path_parts['filename']);
9232
            }
9233
        } else {
9234
            $item_title = '';
9235
            $item_description = '';
9236
        }
9237
9238
        if ($id != 0 && is_array($extra_info)) {
9239
            $parent = $extra_info['parent_item_id'];
9240
        } else {
9241
            $parent = 0;
9242
        }
9243
9244
        $sql = "SELECT * FROM $tbl_lp_item WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9245
        $result = Database::query($sql);
9246
        $arrLP = [];
9247
9248
        while ($row = Database::fetch_array($result)) {
9249
            $arrLP[] = [
9250
                'id' => $row['iid'],
9251
                'item_type' => $row['item_type'],
9252
                'title' => $row['title'],
9253
                'path' => $row['path'],
9254
                'description' => $row['description'],
9255
                'parent_item_id' => $row['parent_item_id'],
9256
                'previous_item_id' => $row['previous_item_id'],
9257
                'next_item_id' => $row['next_item_id'],
9258
                'display_order' => $row['display_order'],
9259
                'max_score' => $row['max_score'],
9260
                'min_score' => $row['min_score'],
9261
                'mastery_score' => $row['mastery_score'],
9262
                'prerequisite' => $row['prerequisite'],
9263
            ];
9264
        }
9265
9266
        $this->tree_array($arrLP);
9267
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9268
        unset($this->arrMenu);
9269
9270
        if ($action == 'add') {
9271
            $formHeader = get_lang('CreateTheDocument');
9272
        } else {
9273
            $formHeader = get_lang('EditTheCurrentDocument');
9274
        }
9275
9276
        if ('edit' === $action) {
9277
            $urlAudioIcon = Display::url(
9278
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9279
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9280
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9281
            );
9282
        } else {
9283
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9284
        }
9285
9286
        $form = new FormValidator(
9287
            'frm_add_reading',
9288
            'POST',
9289
            $this->getCurrentBuildingModeURL(),
9290
            '',
9291
            ['enctype' => 'multipart/form-data']
9292
        );
9293
        $form->addHeader($formHeader);
9294
        $form->addHtml(
9295
            Display::return_message(
9296
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9297
                'normal',
9298
                false
9299
            )
9300
        );
9301
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9302
        $defaults['description'] = $item_description;
9303
9304
        $data = $this->generate_lp_folder($_course);
9305
9306
        if ($action != 'edit') {
9307
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9308
            DocumentManager::build_directory_selector(
9309
                $folders,
9310
                '',
9311
                [],
9312
                true,
9313
                $form,
9314
                'directory_parent_id'
9315
            );
9316
        }
9317
9318
        if (isset($data['id'])) {
9319
            $defaults['directory_parent_id'] = $data['id'];
9320
        }
9321
9322
        $form->addElement(
9323
            'text',
9324
            'title',
9325
            get_lang('Title')
9326
        );
9327
        $form->applyFilter('title', 'trim');
9328
        $form->applyFilter('title', 'html_filter');
9329
9330
        $arrHide[0]['value'] = $this->name;
9331
        $arrHide[0]['padding'] = 20;
9332
9333
        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...
9334
            if ($action != 'add') {
9335
                if ($arrLP[$i]['item_type'] == 'dir' &&
9336
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9337
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9338
                ) {
9339
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9340
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9341
                }
9342
            } else {
9343
                if ($arrLP[$i]['item_type'] == 'dir') {
9344
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9345
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9346
                }
9347
            }
9348
        }
9349
9350
        $parent_select = $form->addSelect(
9351
            'parent',
9352
            get_lang('Parent'),
9353
            [],
9354
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9355
        );
9356
9357
        $my_count = 0;
9358
        foreach ($arrHide as $key => $value) {
9359
            if ($my_count != 0) {
9360
                // The LP name is also the first section and is not in the same charset like the other sections.
9361
                $value['value'] = Security::remove_XSS($value['value']);
9362
                $parent_select->addOption(
9363
                    $value['value'],
9364
                    $key,
9365
                    'style="padding-left:'.$value['padding'].'px;"'
9366
                );
9367
            } else {
9368
                $value['value'] = Security::remove_XSS($value['value']);
9369
                $parent_select->addOption(
9370
                    $value['value'],
9371
                    $key,
9372
                    'style="padding-left:'.$value['padding'].'px;"'
9373
                );
9374
            }
9375
            $my_count++;
9376
        }
9377
9378
        if (!empty($id)) {
9379
            $parent_select->setSelected($parent);
9380
        } else {
9381
            $parent_item_id = Session::read('parent_item_id', 0);
9382
            $parent_select->setSelected($parent_item_id);
9383
        }
9384
9385
        if (is_array($arrLP)) {
9386
            reset($arrLP);
9387
        }
9388
9389
        $arrHide = [];
9390
        $s_selected_position = null;
9391
9392
        // POSITION
9393
        $lastPosition = null;
9394
9395
        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...
9396
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9397
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9398
            ) {
9399
                if ((isset($extra_info['previous_item_id']) &&
9400
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9401
                ) {
9402
                    $s_selected_position = $arrLP[$i]['id'];
9403
                }
9404
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9405
            }
9406
            $lastPosition = $arrLP[$i]['id'];
9407
        }
9408
9409
        if (empty($s_selected_position)) {
9410
            $s_selected_position = $lastPosition;
9411
        }
9412
9413
        $position = $form->addSelect(
9414
            'previous',
9415
            get_lang('Position'),
9416
            []
9417
        );
9418
        $position->addOption(get_lang('FirstPosition'), 0);
9419
9420
        foreach ($arrHide as $key => $value) {
9421
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9422
            $position->addOption(
9423
                $value['value'],
9424
                $key,
9425
                'style="padding-left:'.$padding.'px;"'
9426
            );
9427
        }
9428
        $position->setSelected($s_selected_position);
9429
9430
        if (is_array($arrLP)) {
9431
            reset($arrLP);
9432
        }
9433
9434
        $arrHide = [];
9435
9436
        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...
9437
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9438
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9439
            ) {
9440
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9441
            }
9442
        }
9443
9444
        if (!$no_display_add) {
9445
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9446
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9447
9448
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9449
                if (!$no_display_edit_textarea) {
9450
                    $content = '';
9451
9452
                    if (isset($_POST['content'])) {
9453
                        $content = stripslashes($_POST['content']);
9454
                    } elseif (is_array($extra_info)) {
9455
                        $content = $this->display_document($extra_info['path'], false, false);
9456
                    } elseif (is_numeric($extra_info)) {
9457
                        $content = $this->display_document($extra_info, false, false);
9458
                    }
9459
9460
                    // A new document, it is in the root of the repository.
9461
                    if (is_array($extra_info) && $extra_info != 'new') {
9462
                    } else {
9463
                        $this->generate_lp_folder($_course);
9464
                    }
9465
9466
                    if ($_GET['action'] == 'add_item') {
9467
                        $text = get_lang('LPCreateDocument');
9468
                    } else {
9469
                        $text = get_lang('SaveDocument');
9470
                    }
9471
9472
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9473
                    $form
9474
                        ->defaultRenderer()
9475
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9476
                    $form->addButtonSave($text, 'submit_button');
9477
                    $defaults['content_lp'] = $content;
9478
                }
9479
            } elseif (is_numeric($extra_info)) {
9480
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9481
9482
                $return = $this->display_document($extra_info, true, true, true);
9483
                $form->addElement('html', $return);
9484
            }
9485
        }
9486
9487
        if (is_numeric($extra_info)) {
9488
            $form->addElement('hidden', 'path', $extra_info);
9489
        } elseif (is_array($extra_info)) {
9490
            $form->addElement('hidden', 'path', $extra_info['path']);
9491
        }
9492
9493
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9494
        $form->addElement('hidden', 'post_time', time());
9495
        $form->setDefaults($defaults);
9496
9497
        return $form->returnForm();
9498
    }
9499
9500
    /**
9501
     * @param array  $courseInfo
9502
     * @param string $content
9503
     * @param string $title
9504
     * @param int    $parentId
9505
     *
9506
     * @throws \Doctrine\ORM\ORMException
9507
     * @throws \Doctrine\ORM\OptimisticLockException
9508
     * @throws \Doctrine\ORM\TransactionRequiredException
9509
     *
9510
     * @return int
9511
     */
9512
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9513
    {
9514
        $creatorId = api_get_user_id();
9515
        $sessionId = api_get_session_id();
9516
9517
        // Generates folder
9518
        $result = $this->generate_lp_folder($courseInfo);
9519
        $dir = $result['dir'];
9520
9521
        if (empty($parentId) || $parentId == '/') {
9522
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9523
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9524
9525
            if ($parentId === '/') {
9526
                $dir = '/';
9527
            }
9528
9529
            // Please, do not modify this dirname formatting.
9530
            if (strstr($dir, '..')) {
9531
                $dir = '/';
9532
            }
9533
9534
            if (!empty($dir[0]) && $dir[0] == '.') {
9535
                $dir = substr($dir, 1);
9536
            }
9537
            if (!empty($dir[0]) && $dir[0] != '/') {
9538
                $dir = '/'.$dir;
9539
            }
9540
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9541
                $dir .= '/';
9542
            }
9543
        } else {
9544
            $parentInfo = DocumentManager::get_document_data_by_id(
9545
                $parentId,
9546
                $courseInfo['code']
9547
            );
9548
            if (!empty($parentInfo)) {
9549
                $dir = $parentInfo['path'].'/';
9550
            }
9551
        }
9552
9553
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9554
9555
        if (!is_dir($filepath)) {
9556
            $dir = '/';
9557
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9558
        }
9559
9560
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9561
9562
        if (!empty($title)) {
9563
            $title = api_replace_dangerous_char(stripslashes($title));
9564
        } else {
9565
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9566
        }
9567
9568
        $title = disable_dangerous_file($title);
9569
        $filename = $title;
9570
        $content = !empty($content) ? $content : $_POST['content_lp'];
9571
        $tmpFileName = $filename;
9572
9573
        $i = 0;
9574
        while (file_exists($filepath.$tmpFileName.'.html')) {
9575
            $tmpFileName = $filename.'_'.++$i;
9576
        }
9577
9578
        $filename = $tmpFileName.'.html';
9579
        $content = stripslashes($content);
9580
9581
        if (file_exists($filepath.$filename)) {
9582
            return 0;
9583
        }
9584
9585
        $putContent = file_put_contents($filepath.$filename, $content);
9586
9587
        if ($putContent === false) {
9588
            return 0;
9589
        }
9590
9591
        $fileSize = filesize($filepath.$filename);
9592
        $saveFilePath = $dir.$filename;
9593
9594
        $document = DocumentManager::addDocument(
9595
            $courseInfo,
9596
            $saveFilePath,
9597
            'file',
9598
            $fileSize,
9599
            $tmpFileName,
9600
            '',
9601
            0, //readonly
9602
            true,
9603
            null,
9604
            $sessionId,
9605
            $creatorId
9606
        );
9607
9608
        $documentId = $document->getId();
9609
9610
        if (!$document) {
9611
            return 0;
9612
        }
9613
9614
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9615
        $newTitle = $originalTitle;
9616
9617
        if ($newComment || $newTitle) {
9618
            $em = Database::getManager();
9619
9620
            if ($newComment) {
9621
                $document->setComment($newComment);
9622
            }
9623
9624
            if ($newTitle) {
9625
                $document->setTitle($newTitle);
9626
            }
9627
9628
            $em->persist($document);
9629
            $em->flush();
9630
        }
9631
9632
        return $documentId;
9633
    }
9634
9635
    /**
9636
     * Return HTML form to add/edit a link item.
9637
     *
9638
     * @param string $action     (add/edit)
9639
     * @param int    $id         Item ID if exists
9640
     * @param mixed  $extra_info
9641
     *
9642
     * @throws Exception
9643
     * @throws HTML_QuickForm_Error
9644
     *
9645
     * @return string HTML form
9646
     */
9647
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9648
    {
9649
        $course_id = api_get_course_int_id();
9650
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9651
        $tbl_link = Database::get_course_table(TABLE_LINK);
9652
9653
        $item_title = '';
9654
        $item_description = '';
9655
        $item_url = '';
9656
9657
        if ($id != 0 && is_array($extra_info)) {
9658
            $item_title = stripslashes($extra_info['title']);
9659
            $item_description = stripslashes($extra_info['description']);
9660
            $item_url = stripslashes($extra_info['url']);
9661
        } elseif (is_numeric($extra_info)) {
9662
            $extra_info = (int) $extra_info;
9663
            $sql = "SELECT title, description, url 
9664
                    FROM $tbl_link
9665
                    WHERE c_id = $course_id AND iid = $extra_info";
9666
            $result = Database::query($sql);
9667
            $row = Database::fetch_array($result);
9668
            $item_title = $row['title'];
9669
            $item_description = $row['description'];
9670
            $item_url = $row['url'];
9671
        }
9672
9673
        $form = new FormValidator(
9674
            'edit_link',
9675
            'POST',
9676
            $this->getCurrentBuildingModeURL()
9677
        );
9678
        $defaults = [];
9679
        $parent = 0;
9680
        if ($id != 0 && is_array($extra_info)) {
9681
            $parent = $extra_info['parent_item_id'];
9682
        }
9683
9684
        $sql = "SELECT * FROM $tbl_lp_item
9685
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9686
        $result = Database::query($sql);
9687
        $arrLP = [];
9688
9689
        while ($row = Database::fetch_array($result)) {
9690
            $arrLP[] = [
9691
                'id' => $row['id'],
9692
                'item_type' => $row['item_type'],
9693
                'title' => $row['title'],
9694
                'path' => $row['path'],
9695
                'description' => $row['description'],
9696
                'parent_item_id' => $row['parent_item_id'],
9697
                'previous_item_id' => $row['previous_item_id'],
9698
                'next_item_id' => $row['next_item_id'],
9699
                'display_order' => $row['display_order'],
9700
                'max_score' => $row['max_score'],
9701
                'min_score' => $row['min_score'],
9702
                'mastery_score' => $row['mastery_score'],
9703
                'prerequisite' => $row['prerequisite'],
9704
            ];
9705
        }
9706
9707
        $this->tree_array($arrLP);
9708
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9709
        unset($this->arrMenu);
9710
9711
        if ($action == 'add') {
9712
            $legend = get_lang('CreateTheLink');
9713
        } elseif ($action == 'move') {
9714
            $legend = get_lang('MoveCurrentLink');
9715
        } else {
9716
            $legend = get_lang('EditCurrentLink');
9717
        }
9718
9719
        $form->addHeader($legend);
9720
9721
        if ($action != 'move') {
9722
            $form->addText('title', get_lang('Title'), true, ['class' => 'learnpath_item_form']);
9723
            $defaults['title'] = $item_title;
9724
        }
9725
9726
        $selectParent = $form->addSelect(
9727
            'parent',
9728
            get_lang('Parent'),
9729
            [],
9730
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9731
        );
9732
        $selectParent->addOption($this->name, 0);
9733
        $arrHide = [
9734
            $id,
9735
        ];
9736
9737
        $parent_item_id = Session::read('parent_item_id', 0);
9738
9739
        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...
9740
            if ($action != 'add') {
9741
                if (
9742
                    ($arrLP[$i]['item_type'] == 'dir') &&
9743
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9744
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9745
                ) {
9746
                    $selectParent->addOption(
9747
                        $arrLP[$i]['title'],
9748
                        $arrLP[$i]['id'],
9749
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9750
                    );
9751
9752
                    if ($parent == $arrLP[$i]['id']) {
9753
                        $selectParent->setSelected($arrLP[$i]['id']);
9754
                    }
9755
                } else {
9756
                    $arrHide[] = $arrLP[$i]['id'];
9757
                }
9758
            } else {
9759
                if ($arrLP[$i]['item_type'] == 'dir') {
9760
                    $selectParent->addOption(
9761
                        $arrLP[$i]['title'],
9762
                        $arrLP[$i]['id'],
9763
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9764
                    );
9765
9766
                    if ($parent_item_id == $arrLP[$i]['id']) {
9767
                        $selectParent->setSelected($arrLP[$i]['id']);
9768
                    }
9769
                }
9770
            }
9771
        }
9772
9773
        if (is_array($arrLP)) {
9774
            reset($arrLP);
9775
        }
9776
9777
        $selectPrevious = $form->addSelect(
9778
            'previous',
9779
            get_lang('Position'),
9780
            [],
9781
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9782
        );
9783
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9784
9785
        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...
9786
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9787
                $selectPrevious->addOption(
9788
                    $arrLP[$i]['title'],
9789
                    $arrLP[$i]['id']
9790
                );
9791
9792
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9793
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9794
                } elseif ($action == 'add') {
9795
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9796
                }
9797
            }
9798
        }
9799
9800
        if ($action != 'move') {
9801
            $urlAttributes = ['class' => 'learnpath_item_form'];
9802
9803
            if (is_numeric($extra_info)) {
9804
                $urlAttributes['disabled'] = 'disabled';
9805
            }
9806
9807
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9808
            $defaults['url'] = $item_url;
9809
            $arrHide = [];
9810
            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...
9811
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9812
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9813
                }
9814
            }
9815
        }
9816
9817
        if ($action == 'add') {
9818
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9819
        } else {
9820
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9821
        }
9822
9823
        if ($action == 'move') {
9824
            $form->addHidden('title', $item_title);
9825
            $form->addHidden('description', $item_description);
9826
        }
9827
9828
        if (is_numeric($extra_info)) {
9829
            $form->addHidden('path', $extra_info);
9830
        } elseif (is_array($extra_info)) {
9831
            $form->addHidden('path', $extra_info['path']);
9832
        }
9833
        $form->addHidden('type', TOOL_LINK);
9834
        $form->addHidden('post_time', time());
9835
        $form->setDefaults($defaults);
9836
9837
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9838
    }
9839
9840
    /**
9841
     * Return HTML form to add/edit a student publication (work).
9842
     *
9843
     * @param string $action
9844
     * @param int    $id         Item ID if already exists
9845
     * @param string $extra_info
9846
     *
9847
     * @throws Exception
9848
     *
9849
     * @return string HTML form
9850
     */
9851
    public function display_student_publication_form(
9852
        $action = 'add',
9853
        $id = 0,
9854
        $extra_info = ''
9855
    ) {
9856
        $course_id = api_get_course_int_id();
9857
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9858
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9859
9860
        $item_title = get_lang('Student_publication');
9861
        if ($id != 0 && is_array($extra_info)) {
9862
            $item_title = stripslashes($extra_info['title']);
9863
            $item_description = stripslashes($extra_info['description']);
9864
        } elseif (is_numeric($extra_info)) {
9865
            $extra_info = (int) $extra_info;
9866
            $sql = "SELECT title, description
9867
                    FROM $tbl_publication
9868
                    WHERE c_id = $course_id AND id = ".$extra_info;
9869
9870
            $result = Database::query($sql);
9871
            $row = Database::fetch_array($result);
9872
            if ($row) {
9873
                $item_title = $row['title'];
9874
            }
9875
        }
9876
9877
        $parent = 0;
9878
        if ($id != 0 && is_array($extra_info)) {
9879
            $parent = $extra_info['parent_item_id'];
9880
        }
9881
9882
        $sql = "SELECT * FROM $tbl_lp_item 
9883
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9884
        $result = Database::query($sql);
9885
        $arrLP = [];
9886
9887
        while ($row = Database::fetch_array($result)) {
9888
            $arrLP[] = [
9889
                'id' => $row['iid'],
9890
                'item_type' => $row['item_type'],
9891
                'title' => $row['title'],
9892
                'path' => $row['path'],
9893
                'description' => $row['description'],
9894
                'parent_item_id' => $row['parent_item_id'],
9895
                'previous_item_id' => $row['previous_item_id'],
9896
                'next_item_id' => $row['next_item_id'],
9897
                'display_order' => $row['display_order'],
9898
                'max_score' => $row['max_score'],
9899
                'min_score' => $row['min_score'],
9900
                'mastery_score' => $row['mastery_score'],
9901
                'prerequisite' => $row['prerequisite'],
9902
            ];
9903
        }
9904
9905
        $this->tree_array($arrLP);
9906
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9907
        unset($this->arrMenu);
9908
9909
        $form = new FormValidator('frm_student_publication', 'post', '#');
9910
9911
        if ($action == 'add') {
9912
            $form->addHeader(get_lang('Student_publication'));
9913
        } elseif ($action == 'move') {
9914
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9915
        } else {
9916
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9917
        }
9918
9919
        if ($action != 'move') {
9920
            $form->addText(
9921
                'title',
9922
                get_lang('Title'),
9923
                true,
9924
                ['class' => 'learnpath_item_form', 'id' => 'idTitle']
9925
            );
9926
        }
9927
9928
        $parentSelect = $form->addSelect(
9929
            'parent',
9930
            get_lang('Parent'),
9931
            ['0' => $this->name],
9932
            [
9933
                'onchange' => 'javascript: load_cbo(this.value);',
9934
                'class' => 'learnpath_item_form',
9935
                'id' => 'idParent',
9936
            ]
9937
        );
9938
9939
        $arrHide = [$id];
9940
        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...
9941
            if ($action != 'add') {
9942
                if (
9943
                    ($arrLP[$i]['item_type'] == 'dir') &&
9944
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9945
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9946
                ) {
9947
                    $parentSelect->addOption(
9948
                        $arrLP[$i]['title'],
9949
                        $arrLP[$i]['id'],
9950
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9951
                    );
9952
9953
                    if ($parent == $arrLP[$i]['id']) {
9954
                        $parentSelect->setSelected($arrLP[$i]['id']);
9955
                    }
9956
                } else {
9957
                    $arrHide[] = $arrLP[$i]['id'];
9958
                }
9959
            } else {
9960
                if ($arrLP[$i]['item_type'] == 'dir') {
9961
                    $parentSelect->addOption(
9962
                        $arrLP[$i]['title'],
9963
                        $arrLP[$i]['id'],
9964
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9965
                    );
9966
9967
                    if ($parent == $arrLP[$i]['id']) {
9968
                        $parentSelect->setSelected($arrLP[$i]['id']);
9969
                    }
9970
                }
9971
            }
9972
        }
9973
9974
        if (is_array($arrLP)) {
9975
            reset($arrLP);
9976
        }
9977
9978
        $previousSelect = $form->addSelect(
9979
            'previous',
9980
            get_lang('Position'),
9981
            ['0' => get_lang('FirstPosition')],
9982
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9983
        );
9984
9985
        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...
9986
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9987
                $previousSelect->addOption(
9988
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9989
                    $arrLP[$i]['id']
9990
                );
9991
9992
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9993
                    $previousSelect->setSelected($arrLP[$i]['id']);
9994
                } elseif ($action == 'add') {
9995
                    $previousSelect->setSelected($arrLP[$i]['id']);
9996
                }
9997
            }
9998
        }
9999
10000
        if ($action == 'add') {
10001
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
10002
        } else {
10003
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
10004
        }
10005
10006
        if ($action == 'move') {
10007
            $form->addHidden('title', $item_title);
10008
            $form->addHidden('description', $item_description);
10009
        }
10010
10011
        if (is_numeric($extra_info)) {
10012
            $form->addHidden('path', $extra_info);
10013
        } elseif (is_array($extra_info)) {
10014
            $form->addHidden('path', $extra_info['path']);
10015
        }
10016
10017
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
10018
        $form->addHidden('post_time', time());
10019
        $form->setDefaults(['title' => $item_title]);
10020
10021
        $return = '<div class="sectioncomment">';
10022
        $return .= $form->returnForm();
10023
        $return .= '</div>';
10024
10025
        return $return;
10026
    }
10027
10028
    /**
10029
     * Displays the menu for manipulating a step.
10030
     *
10031
     * @param id     $item_id
10032
     * @param string $item_type
10033
     *
10034
     * @return string
10035
     */
10036
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
10037
    {
10038
        $_course = api_get_course_info();
10039
        $course_code = api_get_course_id();
10040
        $return = '<div class="actions">';
10041
        switch ($item_type) {
10042
            case 'dir':
10043
                // Commented the message cause should not show it.
10044
                //$lang = get_lang('TitleManipulateChapter');
10045
                break;
10046
            case TOOL_LP_FINAL_ITEM:
10047
            case TOOL_DOCUMENT:
10048
                // Commented the message cause should not show it.
10049
                //$lang = get_lang('TitleManipulateDocument');
10050
                break;
10051
            case TOOL_LINK:
10052
            case 'link':
10053
                // Commented the message cause should not show it.
10054
                //$lang = get_lang('TitleManipulateLink');
10055
                break;
10056
            case TOOL_QUIZ:
10057
                // Commented the message cause should not show it.
10058
                //$lang = get_lang('TitleManipulateQuiz');
10059
                break;
10060
            case TOOL_STUDENTPUBLICATION:
10061
                // Commented the message cause should not show it.
10062
                //$lang = get_lang('TitleManipulateStudentPublication');
10063
                break;
10064
        }
10065
10066
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10067
        $item_id = (int) $item_id;
10068
        $sql = "SELECT * FROM $tbl_lp_item 
10069
                WHERE iid = ".$item_id;
10070
        $result = Database::query($sql);
10071
        $row = Database::fetch_assoc($result);
10072
10073
        $audio_player = null;
10074
        // We display an audio player if needed.
10075
        if (!empty($row['audio'])) {
10076
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
10077
10078
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
10079
                .'<audio src="'.$webAudioPath.'" controls>'
10080
                .'</div><br>';
10081
        }
10082
10083
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
10084
10085
        if ($item_type != TOOL_LP_FINAL_ITEM) {
10086
            $return .= Display::url(
10087
                Display::return_icon(
10088
                    'edit.png',
10089
                    get_lang('Edit'),
10090
                    [],
10091
                    ICON_SIZE_SMALL
10092
                ),
10093
                $url.'&action=edit_item&path_item='.$row['path']
10094
            );
10095
10096
            $return .= Display::url(
10097
                Display::return_icon(
10098
                    'move.png',
10099
                    get_lang('Move'),
10100
                    [],
10101
                    ICON_SIZE_SMALL
10102
                ),
10103
                $url.'&action=move_item'
10104
            );
10105
        }
10106
10107
        // Commented for now as prerequisites cannot be added to chapters.
10108
        if ($item_type != 'dir') {
10109
            $return .= Display::url(
10110
                Display::return_icon(
10111
                    'accept.png',
10112
                    get_lang('LearnpathPrerequisites'),
10113
                    [],
10114
                    ICON_SIZE_SMALL
10115
                ),
10116
                $url.'&action=edit_item_prereq'
10117
            );
10118
        }
10119
        $return .= Display::url(
10120
            Display::return_icon(
10121
                'delete.png',
10122
                get_lang('Delete'),
10123
                [],
10124
                ICON_SIZE_SMALL
10125
            ),
10126
            $url.'&action=delete_item'
10127
        );
10128
10129
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
10130
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
10131
            if (empty($documentData)) {
10132
                // Try with iid
10133
                $table = Database::get_course_table(TABLE_DOCUMENT);
10134
                $sql = "SELECT path FROM $table
10135
                        WHERE 
10136
                              c_id = ".api_get_course_int_id()." AND 
10137
                              iid = ".$row['path']." AND 
10138
                              path NOT LIKE '%_DELETED_%'";
10139
                $result = Database::query($sql);
10140
                $documentData = Database::fetch_array($result);
10141
                if ($documentData) {
10142
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
10143
                }
10144
            }
10145
            if (isset($documentData['absolute_path_from_document'])) {
10146
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
10147
            }
10148
        }
10149
10150
        $return .= '</div>';
10151
10152
        if (!empty($audio_player)) {
10153
            $return .= $audio_player;
10154
        }
10155
10156
        return $return;
10157
    }
10158
10159
    /**
10160
     * Creates the javascript needed for filling up the checkboxes without page reload.
10161
     *
10162
     * @return string
10163
     */
10164
    public function get_js_dropdown_array()
10165
    {
10166
        $course_id = api_get_course_int_id();
10167
        $return = 'var child_name = new Array();'."\n";
10168
        $return .= 'var child_value = new Array();'."\n\n";
10169
        $return .= 'child_name[0] = new Array();'."\n";
10170
        $return .= 'child_value[0] = new Array();'."\n\n";
10171
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10172
        $sql = "SELECT * FROM ".$tbl_lp_item."
10173
                WHERE 
10174
                    c_id = $course_id AND 
10175
                    lp_id = ".$this->lp_id." AND 
10176
                    parent_item_id = 0
10177
                ORDER BY display_order ASC";
10178
        $res_zero = Database::query($sql);
10179
        $i = 0;
10180
10181
        while ($row_zero = Database::fetch_array($res_zero)) {
10182
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
10183
                if ($row_zero['item_type'] == TOOL_QUIZ) {
10184
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
10185
                }
10186
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
10187
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
10188
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
10189
            }
10190
        }
10191
        $return .= "\n";
10192
        $sql = "SELECT * FROM $tbl_lp_item
10193
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10194
        $res = Database::query($sql);
10195
        while ($row = Database::fetch_array($res)) {
10196
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
10197
                           WHERE
10198
                                c_id = ".$course_id." AND
10199
                                parent_item_id = ".$row['iid']."
10200
                           ORDER BY display_order ASC";
10201
            $res_parent = Database::query($sql_parent);
10202
            $i = 0;
10203
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
10204
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
10205
10206
            while ($row_parent = Database::fetch_array($res_parent)) {
10207
                $js_var = json_encode(get_lang('After').' '.$row_parent['title']);
10208
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10209
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10210
            }
10211
            $return .= "\n";
10212
        }
10213
10214
        $return .= "
10215
            function load_cbo(id) {
10216
                if (!id) {
10217
                    return false;
10218
                }
10219
            
10220
                var cbo = document.getElementById('previous');
10221
                for(var i = cbo.length - 1; i > 0; i--) {
10222
                    cbo.options[i] = null;
10223
                }
10224
            
10225
                var k=0;
10226
                for(var i = 1; i <= child_name[id].length; i++){
10227
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10228
                    option.style.paddingLeft = '40px';
10229
                    cbo.options[i] = option;
10230
                    k = i;
10231
                }
10232
            
10233
                cbo.options[k].selected = true;
10234
                $('#previous').selectpicker('refresh');
10235
            }";
10236
10237
        return $return;
10238
    }
10239
10240
    /**
10241
     * Display the form to allow moving an item.
10242
     *
10243
     * @param int $item_id Item ID
10244
     *
10245
     * @throws Exception
10246
     * @throws HTML_QuickForm_Error
10247
     *
10248
     * @return string HTML form
10249
     */
10250
    public function display_move_item($item_id)
10251
    {
10252
        $return = '';
10253
        if (is_numeric($item_id)) {
10254
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10255
10256
            $sql = "SELECT * FROM $tbl_lp_item
10257
                    WHERE iid = $item_id";
10258
            $res = Database::query($sql);
10259
            $row = Database::fetch_array($res);
10260
10261
            switch ($row['item_type']) {
10262
                case 'dir':
10263
                case 'asset':
10264
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10265
                    $return .= $this->display_item_form(
10266
                        $row['item_type'],
10267
                        get_lang('MoveCurrentChapter'),
10268
                        'move',
10269
                        $item_id,
10270
                        $row
10271
                    );
10272
                    break;
10273
                case TOOL_DOCUMENT:
10274
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10275
                    $return .= $this->display_document_form('move', $item_id, $row);
10276
                    break;
10277
                case TOOL_LINK:
10278
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10279
                    $return .= $this->display_link_form('move', $item_id, $row);
10280
                    break;
10281
                case TOOL_HOTPOTATOES:
10282
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10283
                    $return .= $this->display_link_form('move', $item_id, $row);
10284
                    break;
10285
                case TOOL_QUIZ:
10286
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10287
                    $return .= $this->display_quiz_form('move', $item_id, $row);
10288
                    break;
10289
                case TOOL_STUDENTPUBLICATION:
10290
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10291
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
10292
                    break;
10293
                case TOOL_FORUM:
10294
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10295
                    $return .= $this->display_forum_form('move', $item_id, $row);
10296
                    break;
10297
                case TOOL_THREAD:
10298
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
10299
                    $return .= $this->display_forum_form('move', $item_id, $row);
10300
                    break;
10301
            }
10302
        }
10303
10304
        return $return;
10305
    }
10306
10307
    /**
10308
     * Return HTML form to allow prerequisites selection.
10309
     *
10310
     * @todo use FormValidator
10311
     *
10312
     * @param int Item ID
10313
     *
10314
     * @return string HTML form
10315
     */
10316
    public function display_item_prerequisites_form($item_id = 0)
10317
    {
10318
        $course_id = api_get_course_int_id();
10319
        $item_id = (int) $item_id;
10320
10321
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10322
10323
        /* Current prerequisite */
10324
        $sql = "SELECT * FROM $tbl_lp_item
10325
                WHERE iid = $item_id";
10326
        $result = Database::query($sql);
10327
        $row = Database::fetch_array($result);
10328
        $prerequisiteId = $row['prerequisite'];
10329
        $return = '<legend>';
10330
        $return .= get_lang('AddEditPrerequisites');
10331
        $return .= '</legend>';
10332
        $return .= '<form method="POST">';
10333
        $return .= '<div class="table-responsive">';
10334
        $return .= '<table class="table table-hover">';
10335
        $return .= '<thead>';
10336
        $return .= '<tr>';
10337
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10338
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10339
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10340
        $return .= '</tr>';
10341
        $return .= '</thead>';
10342
10343
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10344
        $return .= '<tbody>';
10345
        $return .= '<tr>';
10346
        $return .= '<td colspan="3">';
10347
        $return .= '<div class="radio learnpath"><label for="idNone">';
10348
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10349
        $return .= get_lang('None').'</label>';
10350
        $return .= '</div>';
10351
        $return .= '</tr>';
10352
10353
        $sql = "SELECT * FROM $tbl_lp_item
10354
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10355
        $result = Database::query($sql);
10356
        $arrLP = [];
10357
10358
        $selectedMinScore = [];
10359
        $selectedMaxScore = [];
10360
        $masteryScore = [];
10361
        while ($row = Database::fetch_array($result)) {
10362
            if ($row['iid'] == $item_id) {
10363
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10364
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10365
            }
10366
            $masteryScore[$row['iid']] = $row['mastery_score'];
10367
10368
            $arrLP[] = [
10369
                'id' => $row['iid'],
10370
                'item_type' => $row['item_type'],
10371
                'title' => $row['title'],
10372
                'ref' => $row['ref'],
10373
                'description' => $row['description'],
10374
                'parent_item_id' => $row['parent_item_id'],
10375
                'previous_item_id' => $row['previous_item_id'],
10376
                'next_item_id' => $row['next_item_id'],
10377
                'max_score' => $row['max_score'],
10378
                'min_score' => $row['min_score'],
10379
                'mastery_score' => $row['mastery_score'],
10380
                'prerequisite' => $row['prerequisite'],
10381
                'display_order' => $row['display_order'],
10382
                'prerequisite_min_score' => $row['prerequisite_min_score'],
10383
                'prerequisite_max_score' => $row['prerequisite_max_score'],
10384
            ];
10385
        }
10386
10387
        $this->tree_array($arrLP);
10388
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10389
        unset($this->arrMenu);
10390
10391
        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...
10392
            $item = $arrLP[$i];
10393
10394
            if ($item['id'] == $item_id) {
10395
                break;
10396
            }
10397
10398
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10399
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10400
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10401
10402
            $return .= '<tr>';
10403
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10404
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10405
            $return .= '<label for="id'.$item['id'].'">';
10406
            $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'].'" />';
10407
10408
            $icon_name = str_replace(' ', '', $item['item_type']);
10409
10410
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10411
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10412
            } else {
10413
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10414
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10415
                } else {
10416
                    $return .= Display::return_icon('folder_document.png');
10417
                }
10418
            }
10419
10420
            $return .= $item['title'].'</label>';
10421
            $return .= '</div>';
10422
            $return .= '</td>';
10423
10424
            if ($item['item_type'] == TOOL_QUIZ) {
10425
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10426
                $lpItemObj = new LpItem($course_id, $item['id']);
10427
                $exercise = new Exercise($course_id);
10428
                $exercise->read($lpItemObj->path);
10429
                $lpItemObj->max_score = $exercise->get_max_score();
10430
                $lpItemObj->update();
10431
                $item['max_score'] = $lpItemObj->max_score;
10432
10433
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10434
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10435
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10436
                }
10437
10438
                $return .= '<td>';
10439
                $return .= '<input 
10440
                    class="form-control" 
10441
                    size="4" maxlength="3" 
10442
                    name="min_'.$item['id'].'" 
10443
                    type="number" 
10444
                    min="0" 
10445
                    step="1" 
10446
                    max="'.$item['max_score'].'" 
10447
                    value="'.$selectedMinScoreValue.'" 
10448
                />';
10449
                $return .= '</td>';
10450
                $return .= '<td>';
10451
                $return .= '<input 
10452
                    class="form-control" 
10453
                    size="4" 
10454
                    maxlength="3" 
10455
                    name="max_'.$item['id'].'" 
10456
                    type="number" 
10457
                    min="0" 
10458
                    step="1" 
10459
                    max="'.$item['max_score'].'" 
10460
                    value="'.$selectedMaxScoreValue.'" 
10461
                />';
10462
                $return .= '</td>';
10463
            }
10464
10465
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10466
                $return .= '<td>';
10467
                $return .= '<input 
10468
                    size="4" 
10469
                    maxlength="3" 
10470
                    name="min_'.$item['id'].'" 
10471
                    type="number" 
10472
                    min="0" 
10473
                    step="1" 
10474
                    max="'.$item['max_score'].'" 
10475
                    value="'.$selectedMinScoreValue.'" 
10476
                />';
10477
                $return .= '</td>';
10478
                $return .= '<td>';
10479
                $return .= '<input 
10480
                    size="4" 
10481
                    maxlength="3" 
10482
                    name="max_'.$item['id'].'" 
10483
                    type="number" 
10484
                    min="0" 
10485
                    step="1" 
10486
                    max="'.$item['max_score'].'" 
10487
                    value="'.$selectedMaxScoreValue.'" 
10488
                />';
10489
                $return .= '</td>';
10490
            }
10491
            $return .= '</tr>';
10492
        }
10493
        $return .= '<tr>';
10494
        $return .= '</tr>';
10495
        $return .= '</tbody>';
10496
        $return .= '</table>';
10497
        $return .= '</div>';
10498
        $return .= '<div class="form-group">';
10499
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10500
            get_lang('ModifyPrerequisites').'</button>';
10501
        $return .= '</form>';
10502
10503
        return $return;
10504
    }
10505
10506
    /**
10507
     * Return HTML list to allow prerequisites selection for lp.
10508
     *
10509
     * @return string HTML form
10510
     */
10511
    public function display_lp_prerequisites_list()
10512
    {
10513
        $course_id = api_get_course_int_id();
10514
        $lp_id = $this->lp_id;
10515
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10516
10517
        // get current prerequisite
10518
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10519
        $result = Database::query($sql);
10520
        $row = Database::fetch_array($result);
10521
        $prerequisiteId = $row['prerequisite'];
10522
        $session_id = api_get_session_id();
10523
        $session_condition = api_get_session_condition($session_id, true, true);
10524
        $sql = "SELECT * FROM $tbl_lp
10525
                WHERE c_id = $course_id $session_condition
10526
                ORDER BY display_order ";
10527
        $rs = Database::query($sql);
10528
        $return = '';
10529
        $return .= '<select name="prerequisites" class="form-control">';
10530
        $return .= '<option value="0">'.get_lang('None').'</option>';
10531
        if (Database::num_rows($rs) > 0) {
10532
            while ($row = Database::fetch_array($rs)) {
10533
                if ($row['id'] == $lp_id) {
10534
                    continue;
10535
                }
10536
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10537
            }
10538
        }
10539
        $return .= '</select>';
10540
10541
        return $return;
10542
    }
10543
10544
    /**
10545
     * Creates a list with all the documents in it.
10546
     *
10547
     * @param bool $showInvisibleFiles
10548
     *
10549
     * @throws Exception
10550
     * @throws HTML_QuickForm_Error
10551
     *
10552
     * @return string
10553
     */
10554
    public function get_documents($showInvisibleFiles = false)
10555
    {
10556
        $course_info = api_get_course_info();
10557
        $sessionId = api_get_session_id();
10558
        $documentTree = DocumentManager::get_document_preview(
10559
            $course_info,
10560
            $this->lp_id,
10561
            null,
10562
            $sessionId,
10563
            true,
10564
            null,
10565
            null,
10566
            $showInvisibleFiles,
10567
            true
10568
        );
10569
10570
        $headers = [
10571
            get_lang('Files'),
10572
            get_lang('CreateTheDocument'),
10573
            get_lang('CreateReadOutText'),
10574
            get_lang('Upload'),
10575
        ];
10576
10577
        $form = new FormValidator(
10578
            'form_upload',
10579
            'POST',
10580
            $this->getCurrentBuildingModeURL(),
10581
            '',
10582
            ['enctype' => 'multipart/form-data']
10583
        );
10584
10585
        $folders = DocumentManager::get_all_document_folders(
10586
            api_get_course_info(),
10587
            0,
10588
            true
10589
        );
10590
10591
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10592
10593
        DocumentManager::build_directory_selector(
10594
            $folders,
10595
            $lpPathInfo['id'],
10596
            [],
10597
            true,
10598
            $form,
10599
            'directory_parent_id'
10600
        );
10601
10602
        $group = [
10603
            $form->createElement(
10604
                'radio',
10605
                'if_exists',
10606
                get_lang("UplWhatIfFileExists"),
10607
                get_lang('UplDoNothing'),
10608
                'nothing'
10609
            ),
10610
            $form->createElement(
10611
                'radio',
10612
                'if_exists',
10613
                null,
10614
                get_lang('UplOverwriteLong'),
10615
                'overwrite'
10616
            ),
10617
            $form->createElement(
10618
                'radio',
10619
                'if_exists',
10620
                null,
10621
                get_lang('UplRenameLong'),
10622
                'rename'
10623
            ),
10624
        ];
10625
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10626
10627
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10628
        $defaultFileExistsOption = 'rename';
10629
        if (!empty($fileExistsOption)) {
10630
            $defaultFileExistsOption = $fileExistsOption;
10631
        }
10632
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10633
10634
        // Check box options
10635
        $form->addElement(
10636
            'checkbox',
10637
            'unzip',
10638
            get_lang('Options'),
10639
            get_lang('Uncompress')
10640
        );
10641
10642
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10643
        $form->addMultipleUpload($url);
10644
        $new = $this->display_document_form('add', 0);
10645
        $frmReadOutText = $this->displayFrmReadOutText('add');
10646
        $tabs = Display::tabs(
10647
            $headers,
10648
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10649
            'subtab'
10650
        );
10651
10652
        return $tabs;
10653
    }
10654
10655
    /**
10656
     * Creates a list with all the exercises (quiz) in it.
10657
     *
10658
     * @return string
10659
     */
10660
    public function get_exercises()
10661
    {
10662
        $course_id = api_get_course_int_id();
10663
        $session_id = api_get_session_id();
10664
        $userInfo = api_get_user_info();
10665
10666
        // New for hotpotatoes.
10667
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10668
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10669
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10670
        $condition_session = api_get_session_condition($session_id, true, true);
10671
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
10672
10673
        $activeCondition = ' active <> -1 ';
10674
        if ($setting) {
10675
            $activeCondition = ' active = 1 ';
10676
        }
10677
10678
        $sql_quiz = "SELECT * FROM $tbl_quiz
10679
                     WHERE c_id = $course_id AND $activeCondition $condition_session
10680
                     ORDER BY title ASC";
10681
10682
        $sql_hot = "SELECT * FROM $tbl_doc
10683
                     WHERE c_id = $course_id AND path LIKE '".$uploadPath."/%/%htm%'  $condition_session
10684
                     ORDER BY id ASC";
10685
10686
        $res_quiz = Database::query($sql_quiz);
10687
        $res_hot = Database::query($sql_hot);
10688
10689
        $return = '<ul class="lp_resource">';
10690
        $return .= '<li class="lp_resource_element">';
10691
        $return .= Display::return_icon('new_exercice.png');
10692
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10693
            get_lang('NewExercise').'</a>';
10694
        $return .= '</li>';
10695
10696
        $previewIcon = Display::return_icon(
10697
            'preview_view.png',
10698
            get_lang('Preview')
10699
        );
10700
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10701
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10702
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10703
10704
        // Display hotpotatoes
10705
        while ($row_hot = Database::fetch_array($res_hot)) {
10706
            $link = Display::url(
10707
                $previewIcon,
10708
                $exerciseUrl.'&file='.$row_hot['path'],
10709
                ['target' => '_blank']
10710
            );
10711
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10712
            $return .= '<a class="moved" href="#">';
10713
            $return .= Display::return_icon(
10714
                'move_everywhere.png',
10715
                get_lang('Move'),
10716
                [],
10717
                ICON_SIZE_TINY
10718
            );
10719
            $return .= '</a> ';
10720
            $return .= Display::return_icon('hotpotatoes_s.png');
10721
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10722
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10723
            $return .= '</li>';
10724
        }
10725
10726
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10727
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10728
            $title = strip_tags(
10729
                api_html_entity_decode($row_quiz['title'])
10730
            );
10731
10732
            $visibility = api_get_item_visibility(
10733
                ['real_id' => $course_id],
10734
                TOOL_QUIZ,
10735
                $row_quiz['iid'],
10736
                $session_id
10737
            );
10738
10739
            $link = Display::url(
10740
                $previewIcon,
10741
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10742
                ['target' => '_blank']
10743
            );
10744
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10745
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10746
            $return .= $quizIcon;
10747
            $sessionStar = api_get_session_image(
10748
                $row_quiz['session_id'],
10749
                $userInfo['status']
10750
            );
10751
            $return .= Display::url(
10752
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10753
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10754
                [
10755
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10756
                ]
10757
            );
10758
10759
            $return .= '</li>';
10760
        }
10761
10762
        $return .= '</ul>';
10763
10764
        return $return;
10765
    }
10766
10767
    /**
10768
     * Creates a list with all the links in it.
10769
     *
10770
     * @return string
10771
     */
10772
    public function get_links()
10773
    {
10774
        $selfUrl = api_get_self();
10775
        $courseIdReq = api_get_cidreq();
10776
        $course = api_get_course_info();
10777
        $userInfo = api_get_user_info();
10778
10779
        $course_id = $course['real_id'];
10780
        $tbl_link = Database::get_course_table(TABLE_LINK);
10781
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10782
        $moveEverywhereIcon = Display::return_icon(
10783
            'move_everywhere.png',
10784
            get_lang('Move'),
10785
            [],
10786
            ICON_SIZE_TINY
10787
        );
10788
10789
        $session_id = api_get_session_id();
10790
        $condition_session = api_get_session_condition(
10791
            $session_id,
10792
            true,
10793
            true,
10794
            "link.session_id"
10795
        );
10796
10797
        $sql = "SELECT 
10798
                    link.id as link_id,
10799
                    link.title as link_title,
10800
                    link.session_id as link_session_id,
10801
                    link.category_id as category_id,
10802
                    link_category.category_title as category_title
10803
                FROM $tbl_link as link
10804
                LEFT JOIN $linkCategoryTable as link_category
10805
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10806
                WHERE link.c_id = ".$course_id." $condition_session
10807
                ORDER BY link_category.category_title ASC, link.title ASC";
10808
        $result = Database::query($sql);
10809
        $categorizedLinks = [];
10810
        $categories = [];
10811
10812
        while ($link = Database::fetch_array($result)) {
10813
            if (!$link['category_id']) {
10814
                $link['category_title'] = get_lang('Uncategorized');
10815
            }
10816
            $categories[$link['category_id']] = $link['category_title'];
10817
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10818
        }
10819
10820
        $linksHtmlCode =
10821
            '<script>
10822
            function toggle_tool(tool, id) {
10823
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10824
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10825
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10826
                } else {
10827
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10828
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10829
                }
10830
            }
10831
        </script>
10832
10833
        <ul class="lp_resource">
10834
            <li class="lp_resource_element">
10835
                '.Display::return_icon('linksnew.gif').'
10836
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10837
                get_lang('LinkAdd').'
10838
                </a>
10839
            </li>';
10840
10841
        foreach ($categorizedLinks as $categoryId => $links) {
10842
            $linkNodes = null;
10843
            foreach ($links as $key => $linkInfo) {
10844
                $title = $linkInfo['link_title'];
10845
                $linkSessionId = $linkInfo['link_session_id'];
10846
10847
                $link = Display::url(
10848
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10849
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10850
                    ['target' => '_blank']
10851
                );
10852
10853
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10854
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10855
                    $linkNodes .=
10856
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10857
                        <a class="moved" href="#">'.
10858
                            $moveEverywhereIcon.
10859
                        '</a>
10860
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10861
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10862
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10863
                        Security::remove_XSS($title).$sessionStar.$link.
10864
                        '</a>
10865
                    </li>';
10866
                }
10867
            }
10868
            $linksHtmlCode .=
10869
                '<li>
10870
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10871
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10872
                    align="absbottom" />
10873
                </a>
10874
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10875
            </li>
10876
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10877
        }
10878
        $linksHtmlCode .= '</ul>';
10879
10880
        return $linksHtmlCode;
10881
    }
10882
10883
    /**
10884
     * Creates a list with all the student publications in it.
10885
     *
10886
     * @return string
10887
     */
10888
    public function get_student_publications()
10889
    {
10890
        $return = '<ul class="lp_resource">';
10891
        $return .= '<li class="lp_resource_element">';
10892
        $return .= Display::return_icon('works_new.gif');
10893
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10894
            get_lang('AddAssignmentPage').'</a>';
10895
        $return .= '</li>';
10896
10897
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10898
        $works = getWorkListTeacher(0, 100, null, null, null);
10899
        if (!empty($works)) {
10900
            foreach ($works as $work) {
10901
                $link = Display::url(
10902
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10903
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10904
                    ['target' => '_blank']
10905
                );
10906
10907
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10908
                $return .= '<a class="moved" href="#">';
10909
                $return .= Display::return_icon(
10910
                    'move_everywhere.png',
10911
                    get_lang('Move'),
10912
                    [],
10913
                    ICON_SIZE_TINY
10914
                );
10915
                $return .= '</a> ';
10916
10917
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10918
                $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.'">'.
10919
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10920
                </a>';
10921
10922
                $return .= '</li>';
10923
            }
10924
        }
10925
10926
        $return .= '</ul>';
10927
10928
        return $return;
10929
    }
10930
10931
    /**
10932
     * Creates a list with all the forums in it.
10933
     *
10934
     * @return string
10935
     */
10936
    public function get_forums()
10937
    {
10938
        require_once '../forum/forumfunction.inc.php';
10939
10940
        $forumCategories = get_forum_categories();
10941
        $forumsInNoCategory = get_forums_in_category(0);
10942
        if (!empty($forumsInNoCategory)) {
10943
            $forumCategories = array_merge(
10944
                $forumCategories,
10945
                [
10946
                    [
10947
                        'cat_id' => 0,
10948
                        'session_id' => 0,
10949
                        'visibility' => 1,
10950
                        'cat_comment' => null,
10951
                    ],
10952
                ]
10953
            );
10954
        }
10955
10956
        $forumList = get_forums();
10957
        $a_forums = [];
10958
        foreach ($forumCategories as $forumCategory) {
10959
            // The forums in this category.
10960
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10961
            if (!empty($forumsInCategory)) {
10962
                foreach ($forumList as $forum) {
10963
                    if (isset($forum['forum_category']) &&
10964
                        $forum['forum_category'] == $forumCategory['cat_id']
10965
                    ) {
10966
                        $a_forums[] = $forum;
10967
                    }
10968
                }
10969
            }
10970
        }
10971
10972
        $return = '<ul class="lp_resource">';
10973
10974
        // First add link
10975
        $return .= '<li class="lp_resource_element">';
10976
        $return .= Display::return_icon('new_forum.png');
10977
        $return .= Display::url(
10978
            get_lang('CreateANewForum'),
10979
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10980
                'action' => 'add',
10981
                'content' => 'forum',
10982
                'lp_id' => $this->lp_id,
10983
            ]),
10984
            ['title' => get_lang('CreateANewForum')]
10985
        );
10986
        $return .= '</li>';
10987
10988
        $return .= '<script>
10989
            function toggle_forum(forum_id) {
10990
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10991
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10992
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10993
                } else {
10994
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10995
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10996
                }
10997
            }
10998
        </script>';
10999
11000
        foreach ($a_forums as $forum) {
11001
            if (!empty($forum['forum_id'])) {
11002
                $link = Display::url(
11003
                    Display::return_icon('preview_view.png', get_lang('Preview')),
11004
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
11005
                    ['target' => '_blank']
11006
                );
11007
11008
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
11009
                $return .= '<a class="moved" href="#">';
11010
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11011
                $return .= ' </a>';
11012
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
11013
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
11014
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
11015
                            </a>
11016
                            <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">'.
11017
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
11018
11019
                $return .= '</li>';
11020
11021
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
11022
                $a_threads = get_threads($forum['forum_id']);
11023
                if (is_array($a_threads)) {
11024
                    foreach ($a_threads as $thread) {
11025
                        $link = Display::url(
11026
                            Display::return_icon('preview_view.png', get_lang('Preview')),
11027
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
11028
                            ['target' => '_blank']
11029
                        );
11030
11031
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
11032
                        $return .= '&nbsp;<a class="moved" href="#">';
11033
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
11034
                        $return .= ' </a>';
11035
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
11036
                        $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.'">'.
11037
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
11038
                        $return .= '</li>';
11039
                    }
11040
                }
11041
                $return .= '</div>';
11042
            }
11043
        }
11044
        $return .= '</ul>';
11045
11046
        return $return;
11047
    }
11048
11049
    /**
11050
     * // TODO: The output encoding should be equal to the system encoding.
11051
     *
11052
     * Exports the learning path as a SCORM package. This is the main function that
11053
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
11054
     * whole thing and returns the zip.
11055
     *
11056
     * This method needs to be called in PHP5, as it will fail with non-adequate
11057
     * XML package (like the ones for PHP4), and it is *not* a static method, so
11058
     * you need to call it on a learnpath object.
11059
     *
11060
     * @TODO The method might be redefined later on in the scorm class itself to avoid
11061
     * creating a SCORM structure if there is one already. However, if the initial SCORM
11062
     * path has been modified, it should use the generic method here below.
11063
     *
11064
     * @return string Returns the zip package string, or null if error
11065
     */
11066
    public function scormExport()
11067
    {
11068
        api_set_more_memory_and_time_limits();
11069
11070
        $_course = api_get_course_info();
11071
        $course_id = $_course['real_id'];
11072
        // Create the zip handler (this will remain available throughout the method).
11073
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
11074
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
11075
        $temp_dir_short = uniqid('scorm_export', true);
11076
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
11077
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
11078
        $zip_folder = new PclZip($temp_zip_file);
11079
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
11080
        $root_path = $main_path = api_get_path(SYS_PATH);
11081
        $files_cleanup = [];
11082
11083
        // Place to temporarily stash the zip file.
11084
        // create the temp dir if it doesn't exist
11085
        // or do a cleanup before creating the zip file.
11086
        if (!is_dir($temp_zip_dir)) {
11087
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
11088
        } else {
11089
            // Cleanup: Check the temp dir for old files and delete them.
11090
            $handle = opendir($temp_zip_dir);
11091
            while (false !== ($file = readdir($handle))) {
11092
                if ($file != '.' && $file != '..') {
11093
                    unlink("$temp_zip_dir/$file");
11094
                }
11095
            }
11096
            closedir($handle);
11097
        }
11098
        $zip_files = $zip_files_abs = $zip_files_dist = [];
11099
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
11100
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
11101
        ) {
11102
            // Remove the possible . at the end of the path.
11103
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
11104
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
11105
            mkdir(
11106
                $dest_path_to_scorm_folder,
11107
                api_get_permissions_for_new_directories(),
11108
                true
11109
            );
11110
            copyr(
11111
                $current_course_path.'/scorm/'.$this->path,
11112
                $dest_path_to_scorm_folder,
11113
                ['imsmanifest'],
11114
                $zip_files
11115
            );
11116
        }
11117
11118
        // Build a dummy imsmanifest structure.
11119
        // Do not add to the zip yet (we still need it).
11120
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
11121
        // Aggregation Model official document, section "2.3 Content Packaging".
11122
        // We are going to build a UTF-8 encoded manifest.
11123
        // Later we will recode it to the desired (and supported) encoding.
11124
        $xmldoc = new DOMDocument('1.0');
11125
        $root = $xmldoc->createElement('manifest');
11126
        $root->setAttribute('identifier', 'SingleCourseManifest');
11127
        $root->setAttribute('version', '1.1');
11128
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
11129
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
11130
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
11131
        $root->setAttribute(
11132
            'xsi:schemaLocation',
11133
            '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'
11134
        );
11135
        // Build mandatory sub-root container elements.
11136
        $metadata = $xmldoc->createElement('metadata');
11137
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
11138
        $metadata->appendChild($md_schema);
11139
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
11140
        $metadata->appendChild($md_schemaversion);
11141
        $root->appendChild($metadata);
11142
11143
        $organizations = $xmldoc->createElement('organizations');
11144
        $resources = $xmldoc->createElement('resources');
11145
11146
        // Build the only organization we will use in building our learnpaths.
11147
        $organizations->setAttribute('default', 'chamilo_scorm_export');
11148
        $organization = $xmldoc->createElement('organization');
11149
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
11150
        // To set the title of the SCORM entity (=organization), we take the name given
11151
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
11152
        // learning path charset) as it is the encoding that defines how it is stored
11153
        // in the database. Then we convert it to HTML entities again as the "&" character
11154
        // alone is not authorized in XML (must be &amp;).
11155
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
11156
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
11157
        $organization->appendChild($org_title);
11158
        $folder_name = 'document';
11159
11160
        // Removes the learning_path/scorm_folder path when exporting see #4841
11161
        $path_to_remove = '';
11162
        $path_to_replace = '';
11163
        $result = $this->generate_lp_folder($_course);
11164
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11165
            $path_to_remove = 'document'.$result['dir'];
11166
            $path_to_replace = $folder_name.'/';
11167
        }
11168
11169
        // Fixes chamilo scorm exports
11170
        if ($this->ref === 'chamilo_scorm_export') {
11171
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11172
        }
11173
11174
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11175
        $link_updates = [];
11176
        $links_to_create = [];
11177
        foreach ($this->ordered_items as $index => $itemId) {
11178
            /** @var learnpathItem $item */
11179
            $item = $this->items[$itemId];
11180
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11181
                // Get included documents from this item.
11182
                if ($item->type === 'sco') {
11183
                    $inc_docs = $item->get_resources_from_source(
11184
                        null,
11185
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11186
                    );
11187
                } else {
11188
                    $inc_docs = $item->get_resources_from_source();
11189
                }
11190
11191
                // Give a child element <item> to the <organization> element.
11192
                $my_item_id = $item->get_id();
11193
                $my_item = $xmldoc->createElement('item');
11194
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11195
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11196
                $my_item->setAttribute('isvisible', 'true');
11197
                // Give a child element <title> to the <item> element.
11198
                $my_title = $xmldoc->createElement(
11199
                    'title',
11200
                    htmlspecialchars(
11201
                        api_utf8_encode($item->get_title()),
11202
                        ENT_QUOTES,
11203
                        'UTF-8'
11204
                    )
11205
                );
11206
                $my_item->appendChild($my_title);
11207
                // Give a child element <adlcp:prerequisites> to the <item> element.
11208
                $my_prereqs = $xmldoc->createElement(
11209
                    'adlcp:prerequisites',
11210
                    $this->get_scorm_prereq_string($my_item_id)
11211
                );
11212
                $my_prereqs->setAttribute('type', 'aicc_script');
11213
                $my_item->appendChild($my_prereqs);
11214
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11215
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11216
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11217
                //$xmldoc->createElement('adlcp:timelimitaction','');
11218
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11219
                //$xmldoc->createElement('adlcp:datafromlms','');
11220
                // Give a child element <adlcp:masteryscore> to the <item> element.
11221
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11222
                $my_item->appendChild($my_masteryscore);
11223
11224
                // Attach this item to the organization element or hits parent if there is one.
11225
                if (!empty($item->parent) && $item->parent != 0) {
11226
                    $children = $organization->childNodes;
11227
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11228
                    if (is_object($possible_parent)) {
11229
                        $possible_parent->appendChild($my_item);
11230
                    } else {
11231
                        if ($this->debug > 0) {
11232
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11233
                        }
11234
                    }
11235
                } else {
11236
                    if ($this->debug > 0) {
11237
                        error_log('No parent');
11238
                    }
11239
                    $organization->appendChild($my_item);
11240
                }
11241
11242
                // Get the path of the file(s) from the course directory root.
11243
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11244
                $my_xml_file_path = $my_file_path;
11245
                if (!empty($path_to_remove)) {
11246
                    // From docs
11247
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11248
11249
                    // From quiz
11250
                    if ($this->ref === 'chamilo_scorm_export') {
11251
                        $path_to_remove = 'scorm/'.$this->path.'/';
11252
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11253
                    }
11254
                }
11255
11256
                $my_sub_dir = dirname($my_file_path);
11257
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11258
                $my_xml_sub_dir = $my_sub_dir;
11259
                // Give a <resource> child to the <resources> element
11260
                $my_resource = $xmldoc->createElement('resource');
11261
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11262
                $my_resource->setAttribute('type', 'webcontent');
11263
                $my_resource->setAttribute('href', $my_xml_file_path);
11264
                // adlcp:scormtype can be either 'sco' or 'asset'.
11265
                if ($item->type === 'sco') {
11266
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11267
                } else {
11268
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11269
                }
11270
                // xml:base is the base directory to find the files declared in this resource.
11271
                $my_resource->setAttribute('xml:base', '');
11272
                // Give a <file> child to the <resource> element.
11273
                $my_file = $xmldoc->createElement('file');
11274
                $my_file->setAttribute('href', $my_xml_file_path);
11275
                $my_resource->appendChild($my_file);
11276
11277
                // Dependency to other files - not yet supported.
11278
                $i = 1;
11279
                if ($inc_docs) {
11280
                    foreach ($inc_docs as $doc_info) {
11281
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11282
                            continue;
11283
                        }
11284
                        $my_dep = $xmldoc->createElement('resource');
11285
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11286
                        $my_dep->setAttribute('identifier', $res_id);
11287
                        $my_dep->setAttribute('type', 'webcontent');
11288
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11289
                        $my_dep_file = $xmldoc->createElement('file');
11290
                        // Check type of URL.
11291
                        if ($doc_info[1] == 'remote') {
11292
                            // Remote file. Save url as is.
11293
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11294
                            $my_dep->setAttribute('xml:base', '');
11295
                        } elseif ($doc_info[1] === 'local') {
11296
                            switch ($doc_info[2]) {
11297
                                case 'url':
11298
                                    // Local URL - save path as url for now, don't zip file.
11299
                                    $abs_path = api_get_path(SYS_PATH).
11300
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11301
                                    $current_dir = dirname($abs_path);
11302
                                    $current_dir = str_replace('\\', '/', $current_dir);
11303
                                    $file_path = realpath($abs_path);
11304
                                    $file_path = str_replace('\\', '/', $file_path);
11305
                                    $my_dep_file->setAttribute('href', $file_path);
11306
                                    $my_dep->setAttribute('xml:base', '');
11307
                                    if (strstr($file_path, $main_path) !== false) {
11308
                                        // The calculated real path is really inside Chamilo's root path.
11309
                                        // Reduce file path to what's under the DocumentRoot.
11310
                                        $replace = $file_path;
11311
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11312
                                        $destinationFile = $file_path;
11313
11314
                                        if (strstr($file_path, 'upload/users') !== false) {
11315
                                            $pos = strpos($file_path, 'my_files/');
11316
                                            if ($pos !== false) {
11317
                                                $onlyDirectory = str_replace(
11318
                                                    'upload/users/',
11319
                                                    '',
11320
                                                    substr($file_path, $pos, strlen($file_path))
11321
                                                );
11322
                                            }
11323
                                            $replace = $onlyDirectory;
11324
                                            $destinationFile = $replace;
11325
                                        }
11326
                                        $zip_files_abs[] = $file_path;
11327
                                        $link_updates[$my_file_path][] = [
11328
                                            'orig' => $doc_info[0],
11329
                                            'dest' => $destinationFile,
11330
                                            'replace' => $replace,
11331
                                        ];
11332
                                        $my_dep_file->setAttribute('href', $file_path);
11333
                                        $my_dep->setAttribute('xml:base', '');
11334
                                    } elseif (empty($file_path)) {
11335
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11336
                                        $file_path = str_replace('//', '/', $file_path);
11337
                                        if (file_exists($file_path)) {
11338
                                            // We get the relative path.
11339
                                            $file_path = substr($file_path, strlen($current_dir));
11340
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11341
                                            $link_updates[$my_file_path][] = [
11342
                                                'orig' => $doc_info[0],
11343
                                                'dest' => $file_path,
11344
                                            ];
11345
                                            $my_dep_file->setAttribute('href', $file_path);
11346
                                            $my_dep->setAttribute('xml:base', '');
11347
                                        }
11348
                                    }
11349
                                    break;
11350
                                case 'abs':
11351
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11352
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11353
                                    $my_dep->setAttribute('xml:base', '');
11354
11355
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11356
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11357
                                    $abs_img_path_without_subdir = $doc_info[0];
11358
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11359
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11360
                                    if ($pos === 0) {
11361
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11362
                                    }
11363
11364
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11365
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11366
11367
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11368
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11369
                                    // Check if the current document is in that path.
11370
                                    if (strstr($file_path, $cur_path) !== false) {
11371
                                        $destinationFile = substr($file_path, strlen($cur_path));
11372
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11373
11374
                                        $fileToTest = $cur_path.$my_file_path;
11375
                                        if (!empty($path_to_remove)) {
11376
                                            $fileToTest = str_replace(
11377
                                                $path_to_remove.'/',
11378
                                                $path_to_replace,
11379
                                                $cur_path.$my_file_path
11380
                                            );
11381
                                        }
11382
11383
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11384
11385
                                        // Put the current document in the zip (this array is the array
11386
                                        // that will manage documents already in the course folder - relative).
11387
                                        $zip_files[] = $filePathNoCoursePart;
11388
                                        // Update the links to the current document in the
11389
                                        // containing document (make them relative).
11390
                                        $link_updates[$my_file_path][] = [
11391
                                            'orig' => $doc_info[0],
11392
                                            'dest' => $destinationFile,
11393
                                            'replace' => $relative_path,
11394
                                        ];
11395
11396
                                        $my_dep_file->setAttribute('href', $file_path);
11397
                                        $my_dep->setAttribute('xml:base', '');
11398
                                    } elseif (strstr($file_path, $main_path) !== false) {
11399
                                        // The calculated real path is really inside Chamilo's root path.
11400
                                        // Reduce file path to what's under the DocumentRoot.
11401
                                        $file_path = substr($file_path, strlen($root_path));
11402
                                        $zip_files_abs[] = $file_path;
11403
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11404
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11405
                                        $my_dep->setAttribute('xml:base', '');
11406
                                    } elseif (empty($file_path)) {
11407
                                        // Probably this is an image inside "/main" directory
11408
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11409
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11410
11411
                                        if (file_exists($file_path)) {
11412
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11413
                                                // We get the relative path.
11414
                                                $pos = strpos($file_path, 'main/default_course_document/');
11415
                                                if ($pos !== false) {
11416
                                                    $onlyDirectory = str_replace(
11417
                                                        'main/default_course_document/',
11418
                                                        '',
11419
                                                        substr($file_path, $pos, strlen($file_path))
11420
                                                    );
11421
                                                }
11422
11423
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11424
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11425
                                                $zip_files_abs[] = $fileAbs;
11426
                                                $link_updates[$my_file_path][] = [
11427
                                                    'orig' => $doc_info[0],
11428
                                                    'dest' => $destinationFile,
11429
                                                ];
11430
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11431
                                                $my_dep->setAttribute('xml:base', '');
11432
                                            }
11433
                                        }
11434
                                    }
11435
                                    break;
11436
                                case 'rel':
11437
                                    // Path relative to the current document.
11438
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11439
                                    if (substr($doc_info[0], 0, 2) === '..') {
11440
                                        // Relative path going up.
11441
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11442
                                        $current_dir = str_replace('\\', '/', $current_dir);
11443
                                        $file_path = realpath($current_dir.$doc_info[0]);
11444
                                        $file_path = str_replace('\\', '/', $file_path);
11445
                                        if (strstr($file_path, $main_path) !== false) {
11446
                                            // The calculated real path is really inside Chamilo's root path.
11447
                                            // Reduce file path to what's under the DocumentRoot.
11448
                                            $file_path = substr($file_path, strlen($root_path));
11449
                                            $zip_files_abs[] = $file_path;
11450
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11451
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11452
                                            $my_dep->setAttribute('xml:base', '');
11453
                                        }
11454
                                    } else {
11455
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11456
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11457
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11458
                                    }
11459
                                    break;
11460
                                default:
11461
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11462
                                    $my_dep->setAttribute('xml:base', '');
11463
                                    break;
11464
                            }
11465
                        }
11466
                        $my_dep->appendChild($my_dep_file);
11467
                        $resources->appendChild($my_dep);
11468
                        $dependency = $xmldoc->createElement('dependency');
11469
                        $dependency->setAttribute('identifierref', $res_id);
11470
                        $my_resource->appendChild($dependency);
11471
                        $i++;
11472
                    }
11473
                }
11474
                $resources->appendChild($my_resource);
11475
                $zip_files[] = $my_file_path;
11476
            } else {
11477
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11478
                switch ($item->type) {
11479
                    case TOOL_LINK:
11480
                        $my_item = $xmldoc->createElement('item');
11481
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11482
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11483
                        $my_item->setAttribute('isvisible', 'true');
11484
                        // Give a child element <title> to the <item> element.
11485
                        $my_title = $xmldoc->createElement(
11486
                            'title',
11487
                            htmlspecialchars(
11488
                                api_utf8_encode($item->get_title()),
11489
                                ENT_QUOTES,
11490
                                'UTF-8'
11491
                            )
11492
                        );
11493
                        $my_item->appendChild($my_title);
11494
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11495
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11496
                        $my_prereqs->setAttribute('type', 'aicc_script');
11497
                        $my_item->appendChild($my_prereqs);
11498
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11499
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11500
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11501
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11502
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11503
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11504
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11505
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11506
                        $my_item->appendChild($my_masteryscore);
11507
11508
                        // Attach this item to the organization element or its parent if there is one.
11509
                        if (!empty($item->parent) && $item->parent != 0) {
11510
                            $children = $organization->childNodes;
11511
                            for ($i = 0; $i < $children->length; $i++) {
11512
                                $item_temp = $children->item($i);
11513
                                if ($item_temp->nodeName == 'item') {
11514
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11515
                                        $item_temp->appendChild($my_item);
11516
                                    }
11517
                                }
11518
                            }
11519
                        } else {
11520
                            $organization->appendChild($my_item);
11521
                        }
11522
11523
                        $my_file_path = 'link_'.$item->get_id().'.html';
11524
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11525
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11526
                        $rs = Database::query($sql);
11527
                        if ($link = Database::fetch_array($rs)) {
11528
                            $url = $link['url'];
11529
                            $title = stripslashes($link['title']);
11530
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11531
                            $my_xml_file_path = $my_file_path;
11532
                            $my_sub_dir = dirname($my_file_path);
11533
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11534
                            $my_xml_sub_dir = $my_sub_dir;
11535
                            // Give a <resource> child to the <resources> element.
11536
                            $my_resource = $xmldoc->createElement('resource');
11537
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11538
                            $my_resource->setAttribute('type', 'webcontent');
11539
                            $my_resource->setAttribute('href', $my_xml_file_path);
11540
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11541
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11542
                            // xml:base is the base directory to find the files declared in this resource.
11543
                            $my_resource->setAttribute('xml:base', '');
11544
                            // give a <file> child to the <resource> element.
11545
                            $my_file = $xmldoc->createElement('file');
11546
                            $my_file->setAttribute('href', $my_xml_file_path);
11547
                            $my_resource->appendChild($my_file);
11548
                            $resources->appendChild($my_resource);
11549
                        }
11550
                        break;
11551
                    case TOOL_QUIZ:
11552
                        $exe_id = $item->path;
11553
                        // Should be using ref when everything will be cleaned up in this regard.
11554
                        $exe = new Exercise();
11555
                        $exe->read($exe_id);
11556
                        $my_item = $xmldoc->createElement('item');
11557
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11558
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11559
                        $my_item->setAttribute('isvisible', 'true');
11560
                        // Give a child element <title> to the <item> element.
11561
                        $my_title = $xmldoc->createElement(
11562
                            'title',
11563
                            htmlspecialchars(
11564
                                api_utf8_encode($item->get_title()),
11565
                                ENT_QUOTES,
11566
                                'UTF-8'
11567
                            )
11568
                        );
11569
                        $my_item->appendChild($my_title);
11570
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11571
                        $my_item->appendChild($my_max_score);
11572
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11573
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11574
                        $my_prereqs->setAttribute('type', 'aicc_script');
11575
                        $my_item->appendChild($my_prereqs);
11576
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11577
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11578
                        $my_item->appendChild($my_masteryscore);
11579
11580
                        // Attach this item to the organization element or hits parent if there is one.
11581
                        if (!empty($item->parent) && $item->parent != 0) {
11582
                            $children = $organization->childNodes;
11583
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11584
                            if ($possible_parent) {
11585
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11586
                                    $possible_parent->appendChild($my_item);
11587
                                }
11588
                            }
11589
                        } else {
11590
                            $organization->appendChild($my_item);
11591
                        }
11592
11593
                        // Get the path of the file(s) from the course directory root
11594
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11595
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11596
                        // Write the contents of the exported exercise into a (big) html file
11597
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11598
                        $scormExercise = new ScormExercise($exe, true);
11599
                        $contents = $scormExercise->export();
11600
11601
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11602
                        $res = file_put_contents($tmp_file_path, $contents);
11603
                        if ($res === false) {
11604
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11605
                        }
11606
                        $files_cleanup[] = $tmp_file_path;
11607
                        $my_xml_file_path = $my_file_path;
11608
                        $my_sub_dir = dirname($my_file_path);
11609
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11610
                        $my_xml_sub_dir = $my_sub_dir;
11611
                        // Give a <resource> child to the <resources> element.
11612
                        $my_resource = $xmldoc->createElement('resource');
11613
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11614
                        $my_resource->setAttribute('type', 'webcontent');
11615
                        $my_resource->setAttribute('href', $my_xml_file_path);
11616
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11617
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11618
                        // xml:base is the base directory to find the files declared in this resource.
11619
                        $my_resource->setAttribute('xml:base', '');
11620
                        // Give a <file> child to the <resource> element.
11621
                        $my_file = $xmldoc->createElement('file');
11622
                        $my_file->setAttribute('href', $my_xml_file_path);
11623
                        $my_resource->appendChild($my_file);
11624
11625
                        // Get included docs.
11626
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11627
11628
                        // Dependency to other files - not yet supported.
11629
                        $i = 1;
11630
                        foreach ($inc_docs as $doc_info) {
11631
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11632
                                continue;
11633
                            }
11634
                            $my_dep = $xmldoc->createElement('resource');
11635
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11636
                            $my_dep->setAttribute('identifier', $res_id);
11637
                            $my_dep->setAttribute('type', 'webcontent');
11638
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11639
                            $my_dep_file = $xmldoc->createElement('file');
11640
                            // Check type of URL.
11641
                            if ($doc_info[1] == 'remote') {
11642
                                // Remote file. Save url as is.
11643
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11644
                                $my_dep->setAttribute('xml:base', '');
11645
                            } elseif ($doc_info[1] == 'local') {
11646
                                switch ($doc_info[2]) {
11647
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11648
                                        // Save file but as local file (retrieve from URL).
11649
                                        $abs_path = api_get_path(SYS_PATH).
11650
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11651
                                        $current_dir = dirname($abs_path);
11652
                                        $current_dir = str_replace('\\', '/', $current_dir);
11653
                                        $file_path = realpath($abs_path);
11654
                                        $file_path = str_replace('\\', '/', $file_path);
11655
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11656
                                        $my_dep->setAttribute('xml:base', '');
11657
                                        if (strstr($file_path, $main_path) !== false) {
11658
                                            // The calculated real path is really inside the chamilo root path.
11659
                                            // Reduce file path to what's under the DocumentRoot.
11660
                                            $file_path = substr($file_path, strlen($root_path));
11661
                                            $zip_files_abs[] = $file_path;
11662
                                            $link_updates[$my_file_path][] = [
11663
                                                'orig' => $doc_info[0],
11664
                                                'dest' => 'document/'.$file_path,
11665
                                            ];
11666
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11667
                                            $my_dep->setAttribute('xml:base', '');
11668
                                        } elseif (empty($file_path)) {
11669
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11670
                                            $file_path = str_replace('//', '/', $file_path);
11671
                                            if (file_exists($file_path)) {
11672
                                                $file_path = substr($file_path, strlen($current_dir));
11673
                                                // We get the relative path.
11674
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11675
                                                $link_updates[$my_file_path][] = [
11676
                                                    'orig' => $doc_info[0],
11677
                                                    'dest' => 'document/'.$file_path,
11678
                                                ];
11679
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11680
                                                $my_dep->setAttribute('xml:base', '');
11681
                                            }
11682
                                        }
11683
                                        break;
11684
                                    case 'abs':
11685
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11686
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11687
                                        $current_dir = str_replace('\\', '/', $current_dir);
11688
                                        $file_path = realpath($doc_info[0]);
11689
                                        $file_path = str_replace('\\', '/', $file_path);
11690
                                        $my_dep_file->setAttribute('href', $file_path);
11691
                                        $my_dep->setAttribute('xml:base', '');
11692
11693
                                        if (strstr($file_path, $main_path) !== false) {
11694
                                            // The calculated real path is really inside the chamilo root path.
11695
                                            // Reduce file path to what's under the DocumentRoot.
11696
                                            $file_path = substr($file_path, strlen($root_path));
11697
                                            $zip_files_abs[] = $file_path;
11698
                                            $link_updates[$my_file_path][] = [
11699
                                                'orig' => $doc_info[0],
11700
                                                'dest' => $file_path,
11701
                                            ];
11702
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11703
                                            $my_dep->setAttribute('xml:base', '');
11704
                                        } elseif (empty($file_path)) {
11705
                                            $docSysPartPath = str_replace(
11706
                                                api_get_path(REL_COURSE_PATH),
11707
                                                '',
11708
                                                $doc_info[0]
11709
                                            );
11710
11711
                                            $docSysPartPathNoCourseCode = str_replace(
11712
                                                $_course['directory'].'/',
11713
                                                '',
11714
                                                $docSysPartPath
11715
                                            );
11716
11717
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11718
                                            if (file_exists($docSysPath)) {
11719
                                                $file_path = $docSysPartPathNoCourseCode;
11720
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11721
                                                $link_updates[$my_file_path][] = [
11722
                                                    'orig' => $doc_info[0],
11723
                                                    'dest' => $file_path,
11724
                                                ];
11725
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11726
                                                $my_dep->setAttribute('xml:base', '');
11727
                                            }
11728
                                        }
11729
                                        break;
11730
                                    case 'rel':
11731
                                        // Path relative to the current document. Save xml:base as current document's
11732
                                        // directory and save file in zip as subdir.file_path
11733
                                        if (substr($doc_info[0], 0, 2) === '..') {
11734
                                            // Relative path going up.
11735
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11736
                                            $current_dir = str_replace('\\', '/', $current_dir);
11737
                                            $file_path = realpath($current_dir.$doc_info[0]);
11738
                                            $file_path = str_replace('\\', '/', $file_path);
11739
                                            //error_log($file_path.' <-> '.$main_path, 0);
11740
                                            if (strstr($file_path, $main_path) !== false) {
11741
                                                // The calculated real path is really inside Chamilo's root path.
11742
                                                // Reduce file path to what's under the DocumentRoot.
11743
11744
                                                $file_path = substr($file_path, strlen($root_path));
11745
                                                $file_path_dest = $file_path;
11746
11747
                                                // File path is courses/CHAMILO/document/....
11748
                                                $info_file_path = explode('/', $file_path);
11749
                                                if ($info_file_path[0] == 'courses') {
11750
                                                    // Add character "/" in file path.
11751
                                                    $file_path_dest = 'document/'.$file_path;
11752
                                                }
11753
                                                $zip_files_abs[] = $file_path;
11754
11755
                                                $link_updates[$my_file_path][] = [
11756
                                                    'orig' => $doc_info[0],
11757
                                                    'dest' => $file_path_dest,
11758
                                                ];
11759
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11760
                                                $my_dep->setAttribute('xml:base', '');
11761
                                            }
11762
                                        } else {
11763
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11764
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11765
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11766
                                        }
11767
                                        break;
11768
                                    default:
11769
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11770
                                        $my_dep->setAttribute('xml:base', '');
11771
                                        break;
11772
                                }
11773
                            }
11774
                            $my_dep->appendChild($my_dep_file);
11775
                            $resources->appendChild($my_dep);
11776
                            $dependency = $xmldoc->createElement('dependency');
11777
                            $dependency->setAttribute('identifierref', $res_id);
11778
                            $my_resource->appendChild($dependency);
11779
                            $i++;
11780
                        }
11781
                        $resources->appendChild($my_resource);
11782
                        $zip_files[] = $my_file_path;
11783
                        break;
11784
                    default:
11785
                        // Get the path of the file(s) from the course directory root
11786
                        $my_file_path = 'non_exportable.html';
11787
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11788
                        $my_xml_file_path = $my_file_path;
11789
                        $my_sub_dir = dirname($my_file_path);
11790
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11791
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11792
                        $my_xml_sub_dir = $my_sub_dir;
11793
                        // Give a <resource> child to the <resources> element.
11794
                        $my_resource = $xmldoc->createElement('resource');
11795
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11796
                        $my_resource->setAttribute('type', 'webcontent');
11797
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11798
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11799
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11800
                        // xml:base is the base directory to find the files declared in this resource.
11801
                        $my_resource->setAttribute('xml:base', '');
11802
                        // Give a <file> child to the <resource> element.
11803
                        $my_file = $xmldoc->createElement('file');
11804
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11805
                        $my_resource->appendChild($my_file);
11806
                        $resources->appendChild($my_resource);
11807
                        break;
11808
                }
11809
            }
11810
        }
11811
        $organizations->appendChild($organization);
11812
        $root->appendChild($organizations);
11813
        $root->appendChild($resources);
11814
        $xmldoc->appendChild($root);
11815
11816
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11817
11818
        // then add the file to the zip, then destroy the file (this is done automatically).
11819
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11820
        foreach ($zip_files as $file_path) {
11821
            if (empty($file_path)) {
11822
                continue;
11823
            }
11824
11825
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11826
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11827
11828
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11829
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11830
            }
11831
11832
            $this->create_path($dest_file);
11833
            @copy($filePath, $dest_file);
11834
11835
            // Check if the file needs a link update.
11836
            if (in_array($file_path, array_keys($link_updates))) {
11837
                $string = file_get_contents($dest_file);
11838
                unlink($dest_file);
11839
                foreach ($link_updates[$file_path] as $old_new) {
11840
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11841
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11842
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11843
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11844
                    if (substr($old_new['dest'], -3) === 'flv' &&
11845
                        substr($old_new['dest'], 0, 5) === 'main/'
11846
                    ) {
11847
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11848
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11849
                        substr($old_new['dest'], 0, 6) === 'video/'
11850
                    ) {
11851
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11852
                    }
11853
11854
                    // Fix to avoid problems with default_course_document
11855
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11856
                        $newDestination = $old_new['dest'];
11857
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11858
                            $newDestination = $old_new['replace'];
11859
                        }
11860
                    } else {
11861
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11862
                    }
11863
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11864
11865
                    // Add files inside the HTMLs
11866
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11867
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11868
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11869
                        copy(
11870
                            $sys_course_path.$new_path,
11871
                            $destinationFile
11872
                        );
11873
                    }
11874
                }
11875
                file_put_contents($dest_file, $string);
11876
            }
11877
11878
            if (file_exists($filePath) && $copyAll) {
11879
                $extension = $this->get_extension($filePath);
11880
                if (in_array($extension, ['html', 'html'])) {
11881
                    $containerOrigin = dirname($filePath);
11882
                    $containerDestination = dirname($dest_file);
11883
11884
                    $finder = new Finder();
11885
                    $finder->files()->in($containerOrigin)
11886
                        ->notName('*_DELETED_*')
11887
                        ->exclude('share_folder')
11888
                        ->exclude('chat_files')
11889
                        ->exclude('certificates')
11890
                    ;
11891
11892
                    if (is_dir($containerOrigin) &&
11893
                        is_dir($containerDestination)
11894
                    ) {
11895
                        $fs = new Filesystem();
11896
                        $fs->mirror(
11897
                            $containerOrigin,
11898
                            $containerDestination,
11899
                            $finder
11900
                        );
11901
                    }
11902
                }
11903
            }
11904
        }
11905
11906
        foreach ($zip_files_abs as $file_path) {
11907
            if (empty($file_path)) {
11908
                continue;
11909
            }
11910
11911
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11912
                continue;
11913
            }
11914
11915
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11916
            if (strstr($file_path, 'upload/users') !== false) {
11917
                $pos = strpos($file_path, 'my_files/');
11918
                if ($pos !== false) {
11919
                    $onlyDirectory = str_replace(
11920
                        'upload/users/',
11921
                        '',
11922
                        substr($file_path, $pos, strlen($file_path))
11923
                    );
11924
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11925
                }
11926
            }
11927
11928
            if (strstr($file_path, 'default_course_document/') !== false) {
11929
                $replace = str_replace('/main', '', $file_path);
11930
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11931
            }
11932
11933
            if (empty($dest_file)) {
11934
                continue;
11935
            }
11936
11937
            $this->create_path($dest_file);
11938
            copy($main_path.$file_path, $dest_file);
11939
            // Check if the file needs a link update.
11940
            if (in_array($file_path, array_keys($link_updates))) {
11941
                $string = file_get_contents($dest_file);
11942
                unlink($dest_file);
11943
                foreach ($link_updates[$file_path] as $old_new) {
11944
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11945
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11946
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11947
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11948
                    if (substr($old_new['dest'], -3) == 'flv' &&
11949
                        substr($old_new['dest'], 0, 5) == 'main/'
11950
                    ) {
11951
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11952
                    }
11953
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11954
                }
11955
                file_put_contents($dest_file, $string);
11956
            }
11957
        }
11958
11959
        if (is_array($links_to_create)) {
11960
            foreach ($links_to_create as $file => $link) {
11961
                $content = '<!DOCTYPE html><head>
11962
                            <meta charset="'.api_get_language_isocode().'" />
11963
                            <title>'.$link['title'].'</title>
11964
                            </head>
11965
                            <body dir="'.api_get_text_direction().'">
11966
                            <div style="text-align:center">
11967
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11968
                            </body>
11969
                            </html>';
11970
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11971
            }
11972
        }
11973
11974
        // Add non exportable message explanation.
11975
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11976
        $file_content = '<!DOCTYPE html><head>
11977
                        <meta charset="'.api_get_language_isocode().'" />
11978
                        <title>'.$lang_not_exportable.'</title>
11979
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11980
                        </head>
11981
                        <body dir="'.api_get_text_direction().'">';
11982
        $file_content .=
11983
            <<<EOD
11984
                    <style>
11985
            .error-message {
11986
                font-family: arial, verdana, helvetica, sans-serif;
11987
                border-width: 1px;
11988
                border-style: solid;
11989
                left: 50%;
11990
                margin: 10px auto;
11991
                min-height: 30px;
11992
                padding: 5px;
11993
                right: 50%;
11994
                width: 500px;
11995
                background-color: #FFD1D1;
11996
                border-color: #FF0000;
11997
                color: #000;
11998
            }
11999
        </style>
12000
    <body>
12001
        <div class="error-message">
12002
            $lang_not_exportable
12003
        </div>
12004
    </body>
12005
</html>
12006
EOD;
12007
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
12008
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
12009
        }
12010
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
12011
12012
        // Add the extra files that go along with a SCORM package.
12013
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
12014
12015
        $fs = new Filesystem();
12016
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
12017
12018
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
12019
        $manifest = @$xmldoc->saveXML();
12020
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
12021
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
12022
        $zip_folder->add(
12023
            $archivePath.'/'.$temp_dir_short,
12024
            PCLZIP_OPT_REMOVE_PATH,
12025
            $archivePath.'/'.$temp_dir_short.'/'
12026
        );
12027
12028
        // Clean possible temporary files.
12029
        foreach ($files_cleanup as $file) {
12030
            $res = unlink($file);
12031
            if ($res === false) {
12032
                error_log(
12033
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
12034
                    0
12035
                );
12036
            }
12037
        }
12038
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
12039
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
12040
    }
12041
12042
    /**
12043
     * @param int $lp_id
12044
     *
12045
     * @return bool
12046
     */
12047
    public function scorm_export_to_pdf($lp_id)
12048
    {
12049
        $lp_id = (int) $lp_id;
12050
        $files_to_export = [];
12051
        $course_data = api_get_course_info($this->cc);
12052
        if (!empty($course_data)) {
12053
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
12054
            $list = self::get_flat_ordered_items_list($lp_id);
12055
            if (!empty($list)) {
12056
                foreach ($list as $item_id) {
12057
                    $item = $this->items[$item_id];
12058
                    switch ($item->type) {
12059
                        case 'document':
12060
                            //Getting documents from a LP with chamilo documents
12061
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
12062
                            // Try loading document from the base course.
12063
                            if (empty($file_data) && !empty($sessionId)) {
12064
                                $file_data = DocumentManager::get_document_data_by_id(
12065
                                    $item->path,
12066
                                    $this->cc,
12067
                                    false,
12068
                                    0
12069
                                );
12070
                            }
12071
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
12072
                            if (file_exists($file_path)) {
12073
                                $files_to_export[] = [
12074
                                    'title' => $item->get_title(),
12075
                                    'path' => $file_path,
12076
                                ];
12077
                            }
12078
                            break;
12079
                        case 'asset': //commes from a scorm package generated by chamilo
12080
                        case 'sco':
12081
                            $file_path = $scorm_path.'/'.$item->path;
12082
                            if (file_exists($file_path)) {
12083
                                $files_to_export[] = [
12084
                                    'title' => $item->get_title(),
12085
                                    'path' => $file_path,
12086
                                ];
12087
                            }
12088
                            break;
12089
                        case 'dir':
12090
                            $files_to_export[] = [
12091
                                'title' => $item->get_title(),
12092
                                'path' => null,
12093
                            ];
12094
                            break;
12095
                    }
12096
                }
12097
            }
12098
            $pdf = new PDF();
12099
            $result = $pdf->html_to_pdf(
12100
                $files_to_export,
12101
                $this->name,
12102
                $this->cc,
12103
                true
12104
            );
12105
12106
            return $result;
12107
        }
12108
12109
        return false;
12110
    }
12111
12112
    /**
12113
     * Temp function to be moved in main_api or the best place around for this.
12114
     * Creates a file path if it doesn't exist.
12115
     *
12116
     * @param string $path
12117
     */
12118
    public function create_path($path)
12119
    {
12120
        $path_bits = explode('/', dirname($path));
12121
12122
        // IS_WINDOWS_OS has been defined in main_api.lib.php
12123
        $path_built = IS_WINDOWS_OS ? '' : '/';
12124
        foreach ($path_bits as $bit) {
12125
            if (!empty($bit)) {
12126
                $new_path = $path_built.$bit;
12127
                if (is_dir($new_path)) {
12128
                    $path_built = $new_path.'/';
12129
                } else {
12130
                    mkdir($new_path, api_get_permissions_for_new_directories());
12131
                    $path_built = $new_path.'/';
12132
                }
12133
            }
12134
        }
12135
    }
12136
12137
    /**
12138
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
12139
     *
12140
     * @return bool The results of the unlink function, or false if there was no image to start with
12141
     */
12142
    public function delete_lp_image()
12143
    {
12144
        $img = $this->get_preview_image();
12145
        if ($img != '') {
12146
            $del_file = $this->get_preview_image_path(null, 'sys');
12147
            if (isset($del_file) && file_exists($del_file)) {
12148
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
12149
                if (file_exists($del_file_2)) {
12150
                    unlink($del_file_2);
12151
                }
12152
                $this->set_preview_image('');
12153
12154
                return @unlink($del_file);
12155
            }
12156
        }
12157
12158
        return false;
12159
    }
12160
12161
    /**
12162
     * Uploads an author image to the upload/learning_path/images directory.
12163
     *
12164
     * @param array    The image array, coming from the $_FILES superglobal
12165
     *
12166
     * @return bool True on success, false on error
12167
     */
12168
    public function upload_image($image_array)
12169
    {
12170
        if (!empty($image_array['name'])) {
12171
            $upload_ok = process_uploaded_file($image_array);
12172
            $has_attachment = true;
12173
        }
12174
12175
        if ($upload_ok && $has_attachment) {
12176
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12177
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12178
            $updir = $sys_course_path.$courseDir;
12179
            // Try to add an extension to the file if it hasn't one.
12180
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12181
12182
            if (filter_extension($new_file_name)) {
12183
                $file_extension = explode('.', $image_array['name']);
12184
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
12185
                $filename = uniqid('');
12186
                $new_file_name = $filename.'.'.$file_extension;
12187
                $new_path = $updir.'/'.$new_file_name;
12188
12189
                // Resize the image.
12190
                $temp = new Image($image_array['tmp_name']);
12191
                $temp->resize(104);
12192
                $result = $temp->send_image($new_path);
12193
12194
                // Storing the image filename.
12195
                if ($result) {
12196
                    $this->set_preview_image($new_file_name);
12197
12198
                    //Resize to 64px to use on course homepage
12199
                    $temp->resize(64);
12200
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12201
12202
                    return true;
12203
                }
12204
            }
12205
        }
12206
12207
        return false;
12208
    }
12209
12210
    /**
12211
     * @param int    $lp_id
12212
     * @param string $status
12213
     */
12214
    public function set_autolaunch($lp_id, $status)
12215
    {
12216
        $course_id = api_get_course_int_id();
12217
        $lp_id = intval($lp_id);
12218
        $status = intval($status);
12219
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12220
12221
        // Setting everything to autolaunch = 0
12222
        $attributes['autolaunch'] = 0;
12223
        $where = [
12224
            'session_id = ? AND c_id = ? ' => [
12225
                api_get_session_id(),
12226
                $course_id,
12227
            ],
12228
        ];
12229
        Database::update($lp_table, $attributes, $where);
12230
        if ($status == 1) {
12231
            //Setting my lp_id to autolaunch = 1
12232
            $attributes['autolaunch'] = 1;
12233
            $where = [
12234
                'iid = ? AND session_id = ? AND c_id = ?' => [
12235
                    $lp_id,
12236
                    api_get_session_id(),
12237
                    $course_id,
12238
                ],
12239
            ];
12240
            Database::update($lp_table, $attributes, $where);
12241
        }
12242
    }
12243
12244
    /**
12245
     * Gets previous_item_id for the next element of the lp_item table.
12246
     *
12247
     * @author Isaac flores paz
12248
     *
12249
     * @return int Previous item ID
12250
     */
12251
    public function select_previous_item_id()
12252
    {
12253
        $course_id = api_get_course_int_id();
12254
        if ($this->debug > 0) {
12255
            error_log('In learnpath::select_previous_item_id()', 0);
12256
        }
12257
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12258
12259
        // Get the max order of the items
12260
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12261
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12262
        $rs_max_order = Database::query($sql);
12263
        $row_max_order = Database::fetch_object($rs_max_order);
12264
        $max_order = $row_max_order->display_order;
12265
        // Get the previous item ID
12266
        $sql = "SELECT iid as previous FROM $table_lp_item
12267
                WHERE 
12268
                    c_id = $course_id AND 
12269
                    lp_id = ".$this->lp_id." AND 
12270
                    display_order = '$max_order' ";
12271
        $rs_max = Database::query($sql);
12272
        $row_max = Database::fetch_object($rs_max);
12273
12274
        // Return the previous item ID
12275
        return $row_max->previous;
12276
    }
12277
12278
    /**
12279
     * Copies an LP.
12280
     */
12281
    public function copy()
12282
    {
12283
        // Course builder
12284
        $cb = new CourseBuilder();
12285
12286
        //Setting tools that will be copied
12287
        $cb->set_tools_to_build(['learnpaths']);
12288
12289
        //Setting elements that will be copied
12290
        $cb->set_tools_specific_id_list(
12291
            ['learnpaths' => [$this->lp_id]]
12292
        );
12293
12294
        $course = $cb->build();
12295
12296
        //Course restorer
12297
        $course_restorer = new CourseRestorer($course);
12298
        $course_restorer->set_add_text_in_items(true);
12299
        $course_restorer->set_tool_copy_settings(
12300
            ['learnpaths' => ['reset_dates' => true]]
12301
        );
12302
        $course_restorer->restore(
12303
            api_get_course_id(),
12304
            api_get_session_id(),
12305
            false,
12306
            false
12307
        );
12308
    }
12309
12310
    /**
12311
     * Verify document size.
12312
     *
12313
     * @param string $s
12314
     *
12315
     * @return bool
12316
     */
12317
    public static function verify_document_size($s)
12318
    {
12319
        $post_max = ini_get('post_max_size');
12320
        if (substr($post_max, -1, 1) == 'M') {
12321
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12322
        } elseif (substr($post_max, -1, 1) == 'G') {
12323
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12324
        }
12325
        $upl_max = ini_get('upload_max_filesize');
12326
        if (substr($upl_max, -1, 1) == 'M') {
12327
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12328
        } elseif (substr($upl_max, -1, 1) == 'G') {
12329
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12330
        }
12331
        $documents_total_space = DocumentManager::documents_total_space();
12332
        $course_max_space = DocumentManager::get_course_quota();
12333
        $total_size = filesize($s) + $documents_total_space;
12334
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12335
            return true;
12336
        } else {
12337
            return false;
12338
        }
12339
    }
12340
12341
    /**
12342
     * Clear LP prerequisites.
12343
     */
12344
    public function clear_prerequisites()
12345
    {
12346
        $course_id = $this->get_course_int_id();
12347
        if ($this->debug > 0) {
12348
            error_log('In learnpath::clear_prerequisites()', 0);
12349
        }
12350
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12351
        $lp_id = $this->get_id();
12352
        //Cleaning prerequisites
12353
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12354
                WHERE c_id = $course_id AND lp_id = $lp_id";
12355
        Database::query($sql);
12356
12357
        //Cleaning mastery score for exercises
12358
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12359
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12360
        Database::query($sql);
12361
    }
12362
12363
    public function set_previous_step_as_prerequisite_for_all_items()
12364
    {
12365
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12366
        $course_id = $this->get_course_int_id();
12367
        $lp_id = $this->get_id();
12368
12369
        if (!empty($this->items)) {
12370
            $previous_item_id = null;
12371
            $previous_item_max = 0;
12372
            $previous_item_type = null;
12373
            $last_item_not_dir = null;
12374
            $last_item_not_dir_type = null;
12375
            $last_item_not_dir_max = null;
12376
12377
            foreach ($this->ordered_items as $itemId) {
12378
                $item = $this->getItem($itemId);
12379
                // if there was a previous item... (otherwise jump to set it)
12380
                if (!empty($previous_item_id)) {
12381
                    $current_item_id = $item->get_id(); //save current id
12382
                    if ($item->get_type() != 'dir') {
12383
                        // Current item is not a folder, so it qualifies to get a prerequisites
12384
                        if ($last_item_not_dir_type == 'quiz') {
12385
                            // if previous is quiz, mark its max score as default score to be achieved
12386
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12387
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12388
                            Database::query($sql);
12389
                        }
12390
                        // now simply update the prerequisite to set it to the last non-chapter item
12391
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12392
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12393
                        Database::query($sql);
12394
                        // record item as 'non-chapter' reference
12395
                        $last_item_not_dir = $item->get_id();
12396
                        $last_item_not_dir_type = $item->get_type();
12397
                        $last_item_not_dir_max = $item->get_max();
12398
                    }
12399
                } else {
12400
                    if ($item->get_type() != 'dir') {
12401
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12402
                        $last_item_not_dir = $item->get_id();
12403
                        $last_item_not_dir_type = $item->get_type();
12404
                        $last_item_not_dir_max = $item->get_max();
12405
                    }
12406
                }
12407
                // Saving the item as "previous item" for the next loop
12408
                $previous_item_id = $item->get_id();
12409
                $previous_item_max = $item->get_max();
12410
                $previous_item_type = $item->get_type();
12411
            }
12412
        }
12413
    }
12414
12415
    /**
12416
     * @param array $params
12417
     *
12418
     * @throws \Doctrine\ORM\OptimisticLockException
12419
     *
12420
     * @return int
12421
     */
12422
    public static function createCategory($params)
12423
    {
12424
        $em = Database::getManager();
12425
        $item = new CLpCategory();
12426
        $item->setName($params['name']);
12427
        $item->setCId($params['c_id']);
12428
        $em->persist($item);
12429
        $em->flush();
12430
12431
        api_item_property_update(
12432
            api_get_course_info(),
12433
            TOOL_LEARNPATH_CATEGORY,
12434
            $item->getId(),
12435
            'visible',
12436
            api_get_user_id()
12437
        );
12438
12439
        return $item->getId();
12440
    }
12441
12442
    /**
12443
     * @param array $params
12444
     *
12445
     * @throws \Doctrine\ORM\ORMException
12446
     * @throws \Doctrine\ORM\OptimisticLockException
12447
     * @throws \Doctrine\ORM\TransactionRequiredException
12448
     */
12449
    public static function updateCategory($params)
12450
    {
12451
        $em = Database::getManager();
12452
        /** @var CLpCategory $item */
12453
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
12454
        if ($item) {
12455
            $item->setName($params['name']);
12456
            $em->merge($item);
12457
            $em->flush();
12458
        }
12459
    }
12460
12461
    /**
12462
     * @param int $id
12463
     *
12464
     * @throws \Doctrine\ORM\ORMException
12465
     * @throws \Doctrine\ORM\OptimisticLockException
12466
     * @throws \Doctrine\ORM\TransactionRequiredException
12467
     */
12468
    public static function moveUpCategory($id)
12469
    {
12470
        $id = (int) $id;
12471
        $em = Database::getManager();
12472
        /** @var CLpCategory $item */
12473
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12474
        if ($item) {
12475
            $position = $item->getPosition() - 1;
12476
            $item->setPosition($position);
12477
            $em->persist($item);
12478
            $em->flush();
12479
        }
12480
    }
12481
12482
    /**
12483
     * @param int $id
12484
     *
12485
     * @throws \Doctrine\ORM\ORMException
12486
     * @throws \Doctrine\ORM\OptimisticLockException
12487
     * @throws \Doctrine\ORM\TransactionRequiredException
12488
     */
12489
    public static function moveDownCategory($id)
12490
    {
12491
        $id = (int) $id;
12492
        $em = Database::getManager();
12493
        /** @var CLpCategory $item */
12494
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12495
        if ($item) {
12496
            $position = $item->getPosition() + 1;
12497
            $item->setPosition($position);
12498
            $em->persist($item);
12499
            $em->flush();
12500
        }
12501
    }
12502
12503
    /**
12504
     * @param int $courseId
12505
     *
12506
     * @throws \Doctrine\ORM\Query\QueryException
12507
     *
12508
     * @return int|mixed
12509
     */
12510
    public static function getCountCategories($courseId)
12511
    {
12512
        if (empty($courseId)) {
12513
            return 0;
12514
        }
12515
        $em = Database::getManager();
12516
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12517
        $query->setParameter('id', $courseId);
12518
12519
        return $query->getSingleScalarResult();
12520
    }
12521
12522
    /**
12523
     * @param int $courseId
12524
     *
12525
     * @return mixed
12526
     */
12527
    public static function getCategories($courseId)
12528
    {
12529
        $em = Database::getManager();
12530
        //Default behaviour
12531
        /*$items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12532
            array('cId' => $course_id),
12533
            array('name' => 'ASC')
12534
        );*/
12535
12536
        // Using doctrine extensions
12537
        /** @var SortableRepository $repo */
12538
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12539
        $items = $repo
12540
            ->getBySortableGroupsQuery(['cId' => $courseId])
12541
            ->getResult();
12542
12543
        return $items;
12544
    }
12545
12546
    /**
12547
     * @param int $id
12548
     *
12549
     * @throws \Doctrine\ORM\ORMException
12550
     * @throws \Doctrine\ORM\OptimisticLockException
12551
     * @throws \Doctrine\ORM\TransactionRequiredException
12552
     *
12553
     * @return CLpCategory
12554
     */
12555
    public static function getCategory($id)
12556
    {
12557
        $id = (int) $id;
12558
        $em = Database::getManager();
12559
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12560
12561
        return $item;
12562
    }
12563
12564
    /**
12565
     * @param int $courseId
12566
     *
12567
     * @return array
12568
     */
12569
    public static function getCategoryByCourse($courseId)
12570
    {
12571
        $em = Database::getManager();
12572
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
12573
            ['cId' => $courseId]
12574
        );
12575
12576
        return $items;
12577
    }
12578
12579
    /**
12580
     * @param int $id
12581
     *
12582
     * @throws \Doctrine\ORM\ORMException
12583
     * @throws \Doctrine\ORM\OptimisticLockException
12584
     * @throws \Doctrine\ORM\TransactionRequiredException
12585
     *
12586
     * @return mixed
12587
     */
12588
    public static function deleteCategory($id)
12589
    {
12590
        $em = Database::getManager();
12591
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
12592
        if ($item) {
12593
            $courseId = $item->getCId();
12594
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12595
            $query->setParameter('id', $courseId);
12596
            $query->setParameter('catId', $item->getId());
12597
            $lps = $query->getResult();
12598
12599
            // Setting category = 0.
12600
            if ($lps) {
12601
                foreach ($lps as $lpItem) {
12602
                    $lpItem->setCategoryId(0);
12603
                }
12604
            }
12605
12606
            // Removing category.
12607
            $em->remove($item);
12608
            $em->flush();
12609
12610
            $courseInfo = api_get_course_info_by_id($courseId);
12611
            $sessionId = api_get_session_id();
12612
12613
            // Delete link tool
12614
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12615
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12616
            // Delete tools
12617
            $sql = "DELETE FROM $tbl_tool
12618
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12619
            Database::query($sql);
12620
12621
            return true;
12622
        }
12623
12624
        return false;
12625
    }
12626
12627
    /**
12628
     * @param int  $courseId
12629
     * @param bool $addSelectOption
12630
     *
12631
     * @return mixed
12632
     */
12633
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12634
    {
12635
        $items = self::getCategoryByCourse($courseId);
12636
        $cats = [];
12637
        if ($addSelectOption) {
12638
            $cats = [get_lang('SelectACategory')];
12639
        }
12640
12641
        if (!empty($items)) {
12642
            foreach ($items as $cat) {
12643
                $cats[$cat->getId()] = $cat->getName();
12644
            }
12645
        }
12646
12647
        return $cats;
12648
    }
12649
12650
    /**
12651
     * @param string $courseCode
12652
     * @param int    $lpId
12653
     * @param int    $user_id
12654
     *
12655
     * @return learnpath
12656
     */
12657
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12658
    {
12659
        $debug = 0;
12660
        $learnPath = null;
12661
        $lpObject = Session::read('lpobject');
12662
        if ($lpObject !== null) {
12663
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12664
            if ($debug) {
12665
                error_log('getLpFromSession: unserialize');
12666
                error_log('------getLpFromSession------');
12667
                error_log('------unserialize------');
12668
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12669
                error_log("api_get_sessionid: ".api_get_session_id());
12670
            }
12671
        }
12672
12673
        if (!is_object($learnPath)) {
12674
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12675
            if ($debug) {
12676
                error_log('------getLpFromSession------');
12677
                error_log('getLpFromSession: create new learnpath');
12678
                error_log("create new LP with $courseCode - $lpId - $user_id");
12679
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12680
                error_log("api_get_sessionid: ".api_get_session_id());
12681
            }
12682
        }
12683
12684
        return $learnPath;
12685
    }
12686
12687
    /**
12688
     * @param int $itemId
12689
     *
12690
     * @return learnpathItem|false
12691
     */
12692
    public function getItem($itemId)
12693
    {
12694
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12695
            return $this->items[$itemId];
12696
        }
12697
12698
        return false;
12699
    }
12700
12701
    /**
12702
     * @return int
12703
     */
12704
    public function getCategoryId()
12705
    {
12706
        return (int) $this->categoryId;
12707
    }
12708
12709
    /**
12710
     * @param int $categoryId
12711
     *
12712
     * @return bool
12713
     */
12714
    public function setCategoryId($categoryId)
12715
    {
12716
        $this->categoryId = (int) $categoryId;
12717
        $table = Database::get_course_table(TABLE_LP_MAIN);
12718
        $lp_id = $this->get_id();
12719
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12720
                WHERE iid = $lp_id";
12721
        Database::query($sql);
12722
12723
        return true;
12724
    }
12725
12726
    /**
12727
     * Get whether this is a learning path with the possibility to subscribe
12728
     * users or not.
12729
     *
12730
     * @return int
12731
     */
12732
    public function getSubscribeUsers()
12733
    {
12734
        return $this->subscribeUsers;
12735
    }
12736
12737
    /**
12738
     * Set whether this is a learning path with the possibility to subscribe
12739
     * users or not.
12740
     *
12741
     * @param int $value (0 = false, 1 = true)
12742
     *
12743
     * @return bool
12744
     */
12745
    public function setSubscribeUsers($value)
12746
    {
12747
        if ($this->debug > 0) {
12748
            error_log('In learnpath::set_subscribe_users()', 0);
12749
        }
12750
        $this->subscribeUsers = (int) $value;
12751
        $table = Database::get_course_table(TABLE_LP_MAIN);
12752
        $lp_id = $this->get_id();
12753
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12754
                WHERE iid = $lp_id";
12755
        Database::query($sql);
12756
12757
        return true;
12758
    }
12759
12760
    /**
12761
     * Calculate the count of stars for a user in this LP
12762
     * This calculation is based on the following rules:
12763
     * - the student gets one star when he gets to 50% of the learning path
12764
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12765
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12766
     * - the student gets the final star when the score for the *last* test is >= 80%.
12767
     *
12768
     * @param int $sessionId Optional. The session ID
12769
     *
12770
     * @return int The count of stars
12771
     */
12772
    public function getCalculateStars($sessionId = 0)
12773
    {
12774
        $stars = 0;
12775
        $progress = self::getProgress(
12776
            $this->lp_id,
12777
            $this->user_id,
12778
            $this->course_int_id,
12779
            $sessionId
12780
        );
12781
12782
        if ($progress >= 50) {
12783
            $stars++;
12784
        }
12785
12786
        // Calculate stars chapters evaluation
12787
        $exercisesItems = $this->getExercisesItems();
12788
12789
        if (!empty($exercisesItems)) {
12790
            $totalResult = 0;
12791
12792
            foreach ($exercisesItems as $exerciseItem) {
12793
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12794
                    $this->user_id,
12795
                    $exerciseItem->path,
12796
                    $this->course_int_id,
12797
                    $sessionId,
12798
                    $this->lp_id,
12799
                    $exerciseItem->db_id
12800
                );
12801
12802
                $exerciseResultInfo = end($exerciseResultInfo);
12803
12804
                if (!$exerciseResultInfo) {
12805
                    continue;
12806
                }
12807
12808
                if (!empty($exerciseResultInfo['max_score'])) {
12809
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12810
                } else {
12811
                    $exerciseResult = 0;
12812
                }
12813
                $totalResult += $exerciseResult;
12814
            }
12815
12816
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12817
12818
            if ($totalExerciseAverage >= 50) {
12819
                $stars++;
12820
            }
12821
12822
            if ($totalExerciseAverage >= 80) {
12823
                $stars++;
12824
            }
12825
        }
12826
12827
        // Calculate star for final evaluation
12828
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12829
12830
        if (!empty($finalEvaluationItem)) {
12831
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12832
                $this->user_id,
12833
                $finalEvaluationItem->path,
12834
                $this->course_int_id,
12835
                $sessionId,
12836
                $this->lp_id,
12837
                $finalEvaluationItem->db_id
12838
            );
12839
12840
            $evaluationResultInfo = end($evaluationResultInfo);
12841
12842
            if ($evaluationResultInfo) {
12843
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12844
12845
                if ($evaluationResult >= 80) {
12846
                    $stars++;
12847
                }
12848
            }
12849
        }
12850
12851
        return $stars;
12852
    }
12853
12854
    /**
12855
     * Get the items of exercise type.
12856
     *
12857
     * @return array The items. Otherwise return false
12858
     */
12859
    public function getExercisesItems()
12860
    {
12861
        $exercises = [];
12862
        foreach ($this->items as $item) {
12863
            if ($item->type != 'quiz') {
12864
                continue;
12865
            }
12866
            $exercises[] = $item;
12867
        }
12868
12869
        array_pop($exercises);
12870
12871
        return $exercises;
12872
    }
12873
12874
    /**
12875
     * Get the item of exercise type (evaluation type).
12876
     *
12877
     * @return array The final evaluation. Otherwise return false
12878
     */
12879
    public function getFinalEvaluationItem()
12880
    {
12881
        $exercises = [];
12882
        foreach ($this->items as $item) {
12883
            if ($item->type != 'quiz') {
12884
                continue;
12885
            }
12886
12887
            $exercises[] = $item;
12888
        }
12889
12890
        return array_pop($exercises);
12891
    }
12892
12893
    /**
12894
     * Calculate the total points achieved for the current user in this learning path.
12895
     *
12896
     * @param int $sessionId Optional. The session Id
12897
     *
12898
     * @return int
12899
     */
12900
    public function getCalculateScore($sessionId = 0)
12901
    {
12902
        // Calculate stars chapters evaluation
12903
        $exercisesItems = $this->getExercisesItems();
12904
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12905
        $totalExercisesResult = 0;
12906
        $totalEvaluationResult = 0;
12907
12908
        if ($exercisesItems !== false) {
12909
            foreach ($exercisesItems as $exerciseItem) {
12910
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12911
                    $this->user_id,
12912
                    $exerciseItem->path,
12913
                    $this->course_int_id,
12914
                    $sessionId,
12915
                    $this->lp_id,
12916
                    $exerciseItem->db_id
12917
                );
12918
12919
                $exerciseResultInfo = end($exerciseResultInfo);
12920
12921
                if (!$exerciseResultInfo) {
12922
                    continue;
12923
                }
12924
12925
                $totalExercisesResult += $exerciseResultInfo['score'];
12926
            }
12927
        }
12928
12929
        if (!empty($finalEvaluationItem)) {
12930
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12931
                $this->user_id,
12932
                $finalEvaluationItem->path,
12933
                $this->course_int_id,
12934
                $sessionId,
12935
                $this->lp_id,
12936
                $finalEvaluationItem->db_id
12937
            );
12938
12939
            $evaluationResultInfo = end($evaluationResultInfo);
12940
12941
            if ($evaluationResultInfo) {
12942
                $totalEvaluationResult += $evaluationResultInfo['score'];
12943
            }
12944
        }
12945
12946
        return $totalExercisesResult + $totalEvaluationResult;
12947
    }
12948
12949
    /**
12950
     * Check if URL is not allowed to be show in a iframe.
12951
     *
12952
     * @param string $src
12953
     *
12954
     * @return string
12955
     */
12956
    public function fixBlockedLinks($src)
12957
    {
12958
        $urlInfo = parse_url($src);
12959
12960
        $platformProtocol = 'https';
12961
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12962
            $platformProtocol = 'http';
12963
        }
12964
12965
        $protocolFixApplied = false;
12966
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12967
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12968
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12969
12970
        if ($platformProtocol != $scheme) {
12971
            Session::write('x_frame_source', $src);
12972
            $src = 'blank.php?error=x_frames_options';
12973
            $protocolFixApplied = true;
12974
        }
12975
12976
        if ($protocolFixApplied == false) {
12977
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12978
                // Check X-Frame-Options
12979
                $ch = curl_init();
12980
                $options = [
12981
                    CURLOPT_URL => $src,
12982
                    CURLOPT_RETURNTRANSFER => true,
12983
                    CURLOPT_HEADER => true,
12984
                    CURLOPT_FOLLOWLOCATION => true,
12985
                    CURLOPT_ENCODING => "",
12986
                    CURLOPT_AUTOREFERER => true,
12987
                    CURLOPT_CONNECTTIMEOUT => 120,
12988
                    CURLOPT_TIMEOUT => 120,
12989
                    CURLOPT_MAXREDIRS => 10,
12990
                ];
12991
12992
                $proxySettings = api_get_configuration_value('proxy_settings');
12993
                if (!empty($proxySettings) &&
12994
                    isset($proxySettings['curl_setopt_array'])
12995
                ) {
12996
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12997
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12998
                }
12999
13000
                curl_setopt_array($ch, $options);
13001
                $response = curl_exec($ch);
13002
                $httpCode = curl_getinfo($ch);
13003
                $headers = substr($response, 0, $httpCode['header_size']);
13004
13005
                $error = false;
13006
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
13007
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
13008
                ) {
13009
                    $error = true;
13010
                }
13011
13012
                if ($error) {
13013
                    Session::write('x_frame_source', $src);
13014
                    $src = 'blank.php?error=x_frames_options';
13015
                }
13016
            }
13017
        }
13018
13019
        return $src;
13020
    }
13021
13022
    /**
13023
     * Check if this LP has a created forum in the basis course.
13024
     *
13025
     * @return bool
13026
     */
13027
    public function lpHasForum()
13028
    {
13029
        $forumTable = Database::get_course_table(TABLE_FORUM);
13030
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13031
13032
        $fakeFrom = "
13033
            $forumTable f
13034
            INNER JOIN $itemProperty ip
13035
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
13036
        ";
13037
13038
        $resultData = Database::select(
13039
            'COUNT(f.iid) AS qty',
13040
            $fakeFrom,
13041
            [
13042
                'where' => [
13043
                    'ip.visibility != ? AND ' => 2,
13044
                    'ip.tool = ? AND ' => TOOL_FORUM,
13045
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13046
                    'f.lp_id = ?' => intval($this->lp_id),
13047
                ],
13048
            ],
13049
            'first'
13050
        );
13051
13052
        return $resultData['qty'] > 0;
13053
    }
13054
13055
    /**
13056
     * Get the forum for this learning path.
13057
     *
13058
     * @param int $sessionId
13059
     *
13060
     * @return bool
13061
     */
13062
    public function getForum($sessionId = 0)
13063
    {
13064
        $forumTable = Database::get_course_table(TABLE_FORUM);
13065
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
13066
13067
        $fakeFrom = "$forumTable f
13068
            INNER JOIN $itemProperty ip ";
13069
13070
        if ($this->lp_session_id == 0) {
13071
            $fakeFrom .= "
13072
                ON (
13073
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
13074
                        f.session_id = ip.session_id OR ip.session_id IS NULL
13075
                    )
13076
                )
13077
            ";
13078
        } else {
13079
            $fakeFrom .= "
13080
                ON (
13081
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
13082
                )
13083
            ";
13084
        }
13085
13086
        $resultData = Database::select(
13087
            'f.*',
13088
            $fakeFrom,
13089
            [
13090
                'where' => [
13091
                    'ip.visibility != ? AND ' => 2,
13092
                    'ip.tool = ? AND ' => TOOL_FORUM,
13093
                    'f.session_id = ? AND ' => $sessionId,
13094
                    'f.c_id = ? AND ' => intval($this->course_int_id),
13095
                    'f.lp_id = ?' => intval($this->lp_id),
13096
                ],
13097
            ],
13098
            'first'
13099
        );
13100
13101
        if (empty($resultData)) {
13102
            return false;
13103
        }
13104
13105
        return $resultData;
13106
    }
13107
13108
    /**
13109
     * Create a forum for this learning path.
13110
     *
13111
     * @param int $forumCategoryId
13112
     *
13113
     * @return int The forum ID if was created. Otherwise return false
13114
     */
13115
    public function createForum($forumCategoryId)
13116
    {
13117
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13118
13119
        $forumId = store_forum(
13120
            [
13121
                'lp_id' => $this->lp_id,
13122
                'forum_title' => $this->name,
13123
                'forum_comment' => null,
13124
                'forum_category' => intval($forumCategoryId),
13125
                'students_can_edit_group' => ['students_can_edit' => 0],
13126
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13127
                'default_view_type_group' => ['default_view_type' => 'flat'],
13128
                'group_forum' => 0,
13129
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13130
            ],
13131
            [],
13132
            true
13133
        );
13134
13135
        return $forumId;
13136
    }
13137
13138
    /**
13139
     * Get the LP Final Item form.
13140
     *
13141
     * @throws Exception
13142
     * @throws HTML_QuickForm_Error
13143
     *
13144
     * @return string
13145
     */
13146
    public function getFinalItemForm()
13147
    {
13148
        $finalItem = $this->getFinalItem();
13149
        $title = '';
13150
13151
        if ($finalItem) {
13152
            $title = $finalItem->get_title();
13153
            $buttonText = get_lang('Save');
13154
            $content = $this->getSavedFinalItem();
13155
        } else {
13156
            $buttonText = get_lang('LPCreateDocument');
13157
            $content = $this->getFinalItemTemplate();
13158
        }
13159
13160
        $courseInfo = api_get_course_info();
13161
        $result = $this->generate_lp_folder($courseInfo);
13162
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13163
        $relative_prefix = '../../';
13164
13165
        $editorConfig = [
13166
            'ToolbarSet' => 'LearningPathDocuments',
13167
            'Width' => '100%',
13168
            'Height' => '500',
13169
            'FullPage' => true,
13170
            'CreateDocumentDir' => $relative_prefix,
13171
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13172
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13173
        ];
13174
13175
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13176
            'type' => 'document',
13177
            'lp_id' => $this->lp_id,
13178
        ]);
13179
13180
        $form = new FormValidator('final_item', 'POST', $url);
13181
        $form->addText('title', get_lang('Title'));
13182
        $form->addButtonSave($buttonText);
13183
        $form->addHtml(
13184
            Display::return_message(
13185
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13186
                'normal',
13187
                false
13188
            )
13189
        );
13190
13191
        $renderer = $form->defaultRenderer();
13192
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13193
13194
        $form->addHtmlEditor(
13195
            'content_lp_certificate',
13196
            null,
13197
            true,
13198
            false,
13199
            $editorConfig,
13200
            true
13201
        );
13202
        $form->addHidden('action', 'add_final_item');
13203
        $form->addHidden('path', Session::read('pathItem'));
13204
        $form->addHidden('previous', $this->get_last());
13205
        $form->setDefaults(
13206
            ['title' => $title, 'content_lp_certificate' => $content]
13207
        );
13208
13209
        if ($form->validate()) {
13210
            $values = $form->exportValues();
13211
            $lastItemId = $this->getLastInFirstLevel();
13212
13213
            if (!$finalItem) {
13214
                $documentId = $this->create_document(
13215
                    $this->course_info,
13216
                    $values['content_lp_certificate'],
13217
                    $values['title']
13218
                );
13219
                $this->add_item(
13220
                    0,
13221
                    $lastItemId,
13222
                    'final_item',
13223
                    $documentId,
13224
                    $values['title'],
13225
                    ''
13226
                );
13227
13228
                Display::addFlash(
13229
                    Display::return_message(get_lang('Added'))
13230
                );
13231
            } else {
13232
                $this->edit_document($this->course_info);
13233
            }
13234
        }
13235
13236
        return $form->returnForm();
13237
    }
13238
13239
    /**
13240
     * Check if the current lp item is first, both, last or none from lp list.
13241
     *
13242
     * @param int $currentItemId
13243
     *
13244
     * @return string
13245
     */
13246
    public function isFirstOrLastItem($currentItemId)
13247
    {
13248
        if ($this->debug > 0) {
13249
            error_log('In learnpath::isFirstOrLastItem('.$currentItemId.')', 0);
13250
        }
13251
13252
        $lpItemId = [];
13253
        $typeListNotToVerify = self::getChapterTypes();
13254
13255
        // Using get_toc() function instead $this->items because returns the correct order of the items
13256
        foreach ($this->get_toc() as $item) {
13257
            if (!in_array($item['type'], $typeListNotToVerify)) {
13258
                $lpItemId[] = $item['id'];
13259
            }
13260
        }
13261
13262
        $lastLpItemIndex = count($lpItemId) - 1;
13263
        $position = array_search($currentItemId, $lpItemId);
13264
13265
        switch ($position) {
13266
            case 0:
13267
                if (!$lastLpItemIndex) {
13268
                    $answer = 'both';
13269
                    break;
13270
                }
13271
13272
                $answer = 'first';
13273
                break;
13274
            case $lastLpItemIndex:
13275
                $answer = 'last';
13276
                break;
13277
            default:
13278
                $answer = 'none';
13279
        }
13280
13281
        return $answer;
13282
    }
13283
13284
    /**
13285
     * Get whether this is a learning path with the accumulated SCORM time or not.
13286
     *
13287
     * @return int
13288
     */
13289
    public function getAccumulateScormTime()
13290
    {
13291
        return $this->accumulateScormTime;
13292
    }
13293
13294
    /**
13295
     * Set whether this is a learning path with the accumulated SCORM time or not.
13296
     *
13297
     * @param int $value (0 = false, 1 = true)
13298
     *
13299
     * @return bool Always returns true
13300
     */
13301
    public function setAccumulateScormTime($value)
13302
    {
13303
        if ($this->debug > 0) {
13304
            error_log('In learnpath::setAccumulateScormTime()', 0);
13305
        }
13306
        $this->accumulateScormTime = intval($value);
13307
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13308
        $lp_id = $this->get_id();
13309
        $sql = "UPDATE $lp_table 
13310
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13311
                WHERE iid = $lp_id";
13312
        Database::query($sql);
13313
13314
        return true;
13315
    }
13316
13317
    /**
13318
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13319
     * the new learning path tool.
13320
     *
13321
     * The function is a big switch on tool type.
13322
     * In each case, we query the corresponding table for information and build the link
13323
     * with that information.
13324
     *
13325
     * @author Yannick Warnier <[email protected]> - rebranding based on
13326
     * previous work (display_addedresource_link_in_learnpath())
13327
     *
13328
     * @param int $course_id      Course code
13329
     * @param int $learningPathId The learning path ID (in lp table)
13330
     * @param int $id_in_path     the unique index in the items table
13331
     * @param int $lpViewId
13332
     *
13333
     * @return string
13334
     */
13335
    public static function rl_get_resource_link_for_learnpath(
13336
        $course_id,
13337
        $learningPathId,
13338
        $id_in_path,
13339
        $lpViewId
13340
    ) {
13341
        $session_id = api_get_session_id();
13342
        $course_info = api_get_course_info_by_id($course_id);
13343
13344
        $learningPathId = (int) $learningPathId;
13345
        $id_in_path = (int) $id_in_path;
13346
        $lpViewId = (int) $lpViewId;
13347
13348
        $em = Database::getManager();
13349
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13350
13351
        /** @var CLpItem $rowItem */
13352
        $rowItem = $lpItemRepo->findOneBy([
13353
            'cId' => $course_id,
13354
            'lpId' => $learningPathId,
13355
            'iid' => $id_in_path,
13356
        ]);
13357
13358
        if (!$rowItem) {
13359
            // Try one more time with "id"
13360
            /** @var CLpItem $rowItem */
13361
            $rowItem = $lpItemRepo->findOneBy([
13362
                'cId' => $course_id,
13363
                'lpId' => $learningPathId,
13364
                'id' => $id_in_path,
13365
            ]);
13366
13367
            if (!$rowItem) {
13368
                return -1;
13369
            }
13370
        }
13371
13372
        $course_code = $course_info['code'];
13373
        $type = $rowItem->getItemType();
13374
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13375
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13376
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13377
        $link = '';
13378
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13379
13380
        switch ($type) {
13381
            case 'dir':
13382
                return $main_dir_path.'lp/blank.php';
13383
            case TOOL_CALENDAR_EVENT:
13384
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13385
            case TOOL_ANNOUNCEMENT:
13386
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13387
            case TOOL_LINK:
13388
                $linkInfo = Link::getLinkInfo($id);
13389
                if (isset($linkInfo['url'])) {
13390
                    return $linkInfo['url'];
13391
                }
13392
13393
                return '';
13394
            case TOOL_QUIZ:
13395
                if (empty($id)) {
13396
                    return '';
13397
                }
13398
13399
                // Get the lp_item_view with the highest view_count.
13400
                $learnpathItemViewResult = $em
13401
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13402
                    ->findBy(
13403
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13404
                        ['viewCount' => 'DESC'],
13405
                        1
13406
                    );
13407
                /** @var CLpItemView $learnpathItemViewData */
13408
                $learnpathItemViewData = current($learnpathItemViewResult);
13409
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13410
13411
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13412
                    .http_build_query([
13413
                        'lp_init' => 1,
13414
                        'learnpath_item_view_id' => $learnpathItemViewId,
13415
                        'learnpath_id' => $learningPathId,
13416
                        'learnpath_item_id' => $id_in_path,
13417
                        'exerciseId' => $id,
13418
                    ]);
13419
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13420
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13421
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13422
                $myrow = Database::fetch_array($result);
13423
                $path = $myrow['path'];
13424
13425
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13426
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13427
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13428
            case TOOL_FORUM:
13429
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13430
            case TOOL_THREAD:
13431
                // forum post
13432
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13433
                if (empty($id)) {
13434
                    return '';
13435
                }
13436
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13437
                $result = Database::query($sql);
13438
                $myrow = Database::fetch_array($result);
13439
13440
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13441
                    .$extraParams;
13442
            case TOOL_POST:
13443
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13444
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13445
                $myrow = Database::fetch_array($result);
13446
13447
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13448
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13449
            case TOOL_READOUT_TEXT:
13450
                return api_get_path(WEB_CODE_PATH).
13451
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13452
            case TOOL_DOCUMENT:
13453
                $document = $em
13454
                    ->getRepository('ChamiloCourseBundle:CDocument')
13455
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
13456
13457
                if (empty($document)) {
13458
                    // Try with normal id
13459
                    $document = $em
13460
                        ->getRepository('ChamiloCourseBundle:CDocument')
13461
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
13462
13463
                    if (empty($document)) {
13464
                        return '';
13465
                    }
13466
                }
13467
13468
                $documentPathInfo = pathinfo($document->getPath());
13469
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
13470
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13471
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13472
13473
                $openmethod = 2;
13474
                $officedoc = false;
13475
                Session::write('openmethod', $openmethod);
13476
                Session::write('officedoc', $officedoc);
13477
13478
                if ($showDirectUrl) {
13479
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13480
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13481
                        if (Link::isPdfLink($file)) {
13482
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13483
13484
                            return $pdfUrl;
13485
                        }
13486
                    }
13487
13488
                    return $file;
13489
                }
13490
13491
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13492
            case TOOL_LP_FINAL_ITEM:
13493
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13494
                    .$extraParams;
13495
            case 'assignments':
13496
                return $main_dir_path.'work/work.php?'.$extraParams;
13497
            case TOOL_DROPBOX:
13498
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13499
            case 'introduction_text': //DEPRECATED
13500
                return '';
13501
            case TOOL_COURSE_DESCRIPTION:
13502
                return $main_dir_path.'course_description?'.$extraParams;
13503
            case TOOL_GROUP:
13504
                return $main_dir_path.'group/group.php?'.$extraParams;
13505
            case TOOL_USER:
13506
                return $main_dir_path.'user/user.php?'.$extraParams;
13507
            case TOOL_STUDENTPUBLICATION:
13508
                if (!empty($rowItem->getPath())) {
13509
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
13510
                }
13511
13512
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13513
        }
13514
13515
        return $link;
13516
    }
13517
13518
    /**
13519
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13520
     *
13521
     * @author Yannick Warnier <[email protected]>
13522
     *
13523
     * @param string $course_code    Course code
13524
     * @param int    $learningPathId
13525
     * @param int    $id_in_path     The resource ID
13526
     *
13527
     * @return string
13528
     */
13529
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13530
    {
13531
        $_course = api_get_course_info($course_code);
13532
        $course_id = $_course['real_id'];
13533
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13534
        $learningPathId = (int) $learningPathId;
13535
        $id_in_path = (int) $id_in_path;
13536
13537
        $sql = "SELECT item_type, title, ref 
13538
                FROM $tbl_lp_item
13539
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13540
        $res_item = Database::query($sql);
13541
13542
        if (Database::num_rows($res_item) < 1) {
13543
            return '';
13544
        }
13545
        $row_item = Database::fetch_array($res_item);
13546
        $type = strtolower($row_item['item_type']);
13547
        $id = $row_item['ref'];
13548
        $output = '';
13549
13550
        switch ($type) {
13551
            case TOOL_CALENDAR_EVENT:
13552
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13553
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13554
                $myrow = Database::fetch_array($result);
13555
                $output = $myrow['title'];
13556
                break;
13557
            case TOOL_ANNOUNCEMENT:
13558
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13559
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13560
                $myrow = Database::fetch_array($result);
13561
                $output = $myrow['title'];
13562
                break;
13563
            case TOOL_LINK:
13564
                // Doesn't take $target into account.
13565
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13566
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13567
                $myrow = Database::fetch_array($result);
13568
                $output = $myrow['title'];
13569
                break;
13570
            case TOOL_QUIZ:
13571
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13572
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13573
                $myrow = Database::fetch_array($result);
13574
                $output = $myrow['title'];
13575
                break;
13576
            case TOOL_FORUM:
13577
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13578
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13579
                $myrow = Database::fetch_array($result);
13580
                $output = $myrow['forum_name'];
13581
                break;
13582
            case TOOL_THREAD:  //=topics
13583
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13584
                // Grabbing the title of the post.
13585
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13586
                $result_title = Database::query($sql_title);
13587
                $myrow_title = Database::fetch_array($result_title);
13588
                $output = $myrow_title['post_title'];
13589
                break;
13590
            case TOOL_POST:
13591
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13592
                //$tbl_post_text = Database::get_course_table(FORUM_POST_TEXT_TABLE);
13593
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13594
                $result = Database::query($sql);
13595
                $post = Database::fetch_array($result);
13596
                $output = $post['post_title'];
13597
                break;
13598
            case 'dir':
13599
                $title = $row_item['title'];
13600
                if (!empty($title)) {
13601
                    $output = $title;
13602
                } else {
13603
                    $output = '-';
13604
                }
13605
                break;
13606
            case TOOL_DOCUMENT:
13607
                $title = $row_item['title'];
13608
                if (!empty($title)) {
13609
                    $output = $title;
13610
                } else {
13611
                    $output = '-';
13612
                }
13613
                break;
13614
            case 'hotpotatoes':
13615
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13616
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13617
                $myrow = Database::fetch_array($result);
13618
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13619
                $last = count($pathname) - 1; // Making a correct name for the link.
13620
                $filename = $pathname[$last]; // Making a correct name for the link.
13621
                $ext = explode('.', $filename);
13622
                $ext = strtolower($ext[sizeof($ext) - 1]);
13623
                $myrow['path'] = rawurlencode($myrow['path']);
13624
                $output = $filename;
13625
                break;
13626
        }
13627
13628
        return stripslashes($output);
13629
    }
13630
13631
    /**
13632
     * Get the parent names for the current item.
13633
     *
13634
     * @param int $newItemId Optional. The item ID
13635
     *
13636
     * @return array
13637
     */
13638
    public function getCurrentItemParentNames($newItemId = 0)
13639
    {
13640
        $newItemId = $newItemId ?: $this->get_current_item_id();
13641
        $return = [];
13642
        $item = $this->getItem($newItemId);
13643
        $parent = $this->getItem($item->get_parent());
13644
13645
        while ($parent) {
13646
            $return[] = $parent->get_title();
13647
13648
            $parent = $this->getItem($parent->get_parent());
13649
        }
13650
13651
        return array_reverse($return);
13652
    }
13653
13654
    /**
13655
     * Reads and process "lp_subscription_settings" setting.
13656
     *
13657
     * @return array
13658
     */
13659
    public static function getSubscriptionSettings()
13660
    {
13661
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13662
        if (empty($subscriptionSettings)) {
13663
            // By default allow both settings
13664
            $subscriptionSettings = [
13665
                'allow_add_users_to_lp' => true,
13666
                'allow_add_users_to_lp_category' => true,
13667
            ];
13668
        } else {
13669
            $subscriptionSettings = $subscriptionSettings['options'];
13670
        }
13671
13672
        return $subscriptionSettings;
13673
    }
13674
13675
    /**
13676
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13677
     */
13678
    public function exportToCourseBuildFormat()
13679
    {
13680
        if (!api_is_allowed_to_edit()) {
13681
            return false;
13682
        }
13683
13684
        $courseBuilder = new CourseBuilder();
13685
        $itemList = [];
13686
        /** @var learnpathItem $item */
13687
        foreach ($this->items as $item) {
13688
            $itemList[$item->get_type()][] = $item->get_path();
13689
        }
13690
13691
        if (empty($itemList)) {
13692
            return false;
13693
        }
13694
13695
        if (isset($itemList['document'])) {
13696
            // Get parents
13697
            foreach ($itemList['document'] as $documentId) {
13698
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13699
                if (!empty($documentInfo['parents'])) {
13700
                    foreach ($documentInfo['parents'] as $parentInfo) {
13701
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13702
                            continue;
13703
                        }
13704
                        $itemList['document'][] = $parentInfo['iid'];
13705
                    }
13706
                }
13707
            }
13708
13709
            $courseInfo = api_get_course_info();
13710
            foreach ($itemList['document'] as $documentId) {
13711
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13712
                $items = DocumentManager::get_resources_from_source_html(
13713
                    $documentInfo['absolute_path'],
13714
                    true,
13715
                    TOOL_DOCUMENT
13716
                );
13717
13718
                if (!empty($items)) {
13719
                    foreach ($items as $item) {
13720
                        // Get information about source url
13721
                        $url = $item[0]; // url
13722
                        $scope = $item[1]; // scope (local, remote)
13723
                        $type = $item[2]; // type (rel, abs, url)
13724
13725
                        $origParseUrl = parse_url($url);
13726
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13727
13728
                        if ($scope == 'local') {
13729
                            if ($type == 'abs' || $type == 'rel') {
13730
                                $documentFile = strstr($realOrigPath, 'document');
13731
                                if (strpos($realOrigPath, $documentFile) !== false) {
13732
                                    $documentFile = str_replace('document', '', $documentFile);
13733
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13734
                                    // Document found! Add it to the list
13735
                                    if ($itemDocumentId) {
13736
                                        $itemList['document'][] = $itemDocumentId;
13737
                                    }
13738
                                }
13739
                            }
13740
                        }
13741
                    }
13742
                }
13743
            }
13744
13745
            $courseBuilder->build_documents(
13746
                api_get_session_id(),
13747
                $this->get_course_int_id(),
13748
                true,
13749
                $itemList['document']
13750
            );
13751
        }
13752
13753
        if (isset($itemList['quiz'])) {
13754
            $courseBuilder->build_quizzes(
13755
                api_get_session_id(),
13756
                $this->get_course_int_id(),
13757
                true,
13758
                $itemList['quiz']
13759
            );
13760
        }
13761
13762
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13763
13764
        /*if (!empty($itemList['thread'])) {
13765
            $postList = [];
13766
            foreach ($itemList['thread'] as $postId) {
13767
                $post = get_post_information($postId);
13768
                if ($post) {
13769
                    if (!isset($itemList['forum'])) {
13770
                        $itemList['forum'] = [];
13771
                    }
13772
                    $itemList['forum'][] = $post['forum_id'];
13773
                    $postList[] = $postId;
13774
                }
13775
            }
13776
13777
            if (!empty($postList)) {
13778
                $courseBuilder->build_forum_posts(
13779
                    $this->get_course_int_id(),
13780
                    null,
13781
                    null,
13782
                    $postList
13783
                );
13784
            }
13785
        }*/
13786
13787
        if (!empty($itemList['thread'])) {
13788
            $threadList = [];
13789
            $em = Database::getManager();
13790
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13791
            foreach ($itemList['thread'] as $threadId) {
13792
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13793
                $thread = $repo->find($threadId);
13794
                if ($thread) {
13795
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13796
                    $threadList[] = $thread->getIid();
13797
                }
13798
            }
13799
13800
            if (!empty($threadList)) {
13801
                $courseBuilder->build_forum_topics(
13802
                    api_get_session_id(),
13803
                    $this->get_course_int_id(),
13804
                    null,
13805
                    $threadList
13806
                );
13807
            }
13808
        }
13809
13810
        $forumCategoryList = [];
13811
        if (isset($itemList['forum'])) {
13812
            foreach ($itemList['forum'] as $forumId) {
13813
                $forumInfo = get_forums($forumId);
13814
                $forumCategoryList[] = $forumInfo['forum_category'];
13815
            }
13816
        }
13817
13818
        if (!empty($forumCategoryList)) {
13819
            $courseBuilder->build_forum_category(
13820
                api_get_session_id(),
13821
                $this->get_course_int_id(),
13822
                true,
13823
                $forumCategoryList
13824
            );
13825
        }
13826
13827
        if (!empty($itemList['forum'])) {
13828
            $courseBuilder->build_forums(
13829
                api_get_session_id(),
13830
                $this->get_course_int_id(),
13831
                true,
13832
                $itemList['forum']
13833
            );
13834
        }
13835
13836
        if (isset($itemList['link'])) {
13837
            $courseBuilder->build_links(
13838
                api_get_session_id(),
13839
                $this->get_course_int_id(),
13840
                true,
13841
                $itemList['link']
13842
            );
13843
        }
13844
13845
        if (!empty($itemList['student_publication'])) {
13846
            $courseBuilder->build_works(
13847
                api_get_session_id(),
13848
                $this->get_course_int_id(),
13849
                true,
13850
                $itemList['student_publication']
13851
            );
13852
        }
13853
13854
        $courseBuilder->build_learnpaths(
13855
            api_get_session_id(),
13856
            $this->get_course_int_id(),
13857
            true,
13858
            [$this->get_id()],
13859
            false
13860
        );
13861
13862
        $courseBuilder->restoreDocumentsFromList();
13863
13864
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13865
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13866
        $result = DocumentManager::file_send_for_download(
13867
            $zipPath,
13868
            true,
13869
            $this->get_name().'.zip'
13870
        );
13871
13872
        if ($result) {
13873
            api_not_allowed();
13874
        }
13875
13876
        return true;
13877
    }
13878
13879
    /**
13880
     * Get whether this is a learning path with the accumulated work time or not.
13881
     *
13882
     * @return int
13883
     */
13884
    public function getAccumulateWorkTime()
13885
    {
13886
        return (int) $this->accumulateWorkTime;
13887
    }
13888
13889
    /**
13890
     * Get whether this is a learning path with the accumulated work time or not.
13891
     *
13892
     * @return int
13893
     */
13894
    public function getAccumulateWorkTimeTotalCourse()
13895
    {
13896
        $table = Database::get_course_table(TABLE_LP_MAIN);
13897
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13898
                FROM $table 
13899
                WHERE c_id = ".$this->course_int_id;
13900
        $result = Database::query($sql);
13901
        $row = Database::fetch_array($result);
13902
13903
        return (int) $row['total'];
13904
    }
13905
13906
    /**
13907
     * Set whether this is a learning path with the accumulated work time or not.
13908
     *
13909
     * @param int $value (0 = false, 1 = true)
13910
     *
13911
     * @return bool
13912
     */
13913
    public function setAccumulateWorkTime($value)
13914
    {
13915
        if (!api_get_configuration_value('lp_minimum_time')) {
13916
            return false;
13917
        }
13918
13919
        $this->accumulateWorkTime = (int) $value;
13920
        $table = Database::get_course_table(TABLE_LP_MAIN);
13921
        $lp_id = $this->get_id();
13922
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13923
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13924
        Database::query($sql);
13925
13926
        return true;
13927
    }
13928
13929
    /**
13930
     * @param int $lpId
13931
     * @param int $courseId
13932
     *
13933
     * @return mixed
13934
     */
13935
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13936
    {
13937
        $lpId = (int) $lpId;
13938
        $courseId = (int) $courseId;
13939
13940
        $table = Database::get_course_table(TABLE_LP_MAIN);
13941
        $sql = "SELECT accumulate_work_time 
13942
                FROM $table 
13943
                WHERE c_id = $courseId AND id = $lpId";
13944
        $result = Database::query($sql);
13945
        $row = Database::fetch_array($result);
13946
13947
        return $row['accumulate_work_time'];
13948
    }
13949
13950
    /**
13951
     * @param int $courseId
13952
     *
13953
     * @return int
13954
     */
13955
    public static function getAccumulateWorkTimeTotal($courseId)
13956
    {
13957
        $table = Database::get_course_table(TABLE_LP_MAIN);
13958
        $courseId = (int) $courseId;
13959
        $sql = "SELECT SUM(accumulate_work_time) AS total
13960
                FROM $table
13961
                WHERE c_id = $courseId";
13962
        $result = Database::query($sql);
13963
        $row = Database::fetch_array($result);
13964
13965
        return (int) $row['total'];
13966
    }
13967
13968
    /**
13969
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13970
     * and put the images in.
13971
     *
13972
     * @return array
13973
     */
13974
    public static function getIconSelect()
13975
    {
13976
        $theme = api_get_visual_theme();
13977
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13978
        $icons = ['' => get_lang('SelectAnOption')];
13979
13980
        if (is_dir($path)) {
13981
            $finder = new Finder();
13982
            $finder->files()->in($path);
13983
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13984
            /** @var SplFileInfo $file */
13985
            foreach ($finder as $file) {
13986
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13987
                    $icons[$file->getFilename()] = $file->getFilename();
13988
                }
13989
            }
13990
        }
13991
13992
        return $icons;
13993
    }
13994
13995
    /**
13996
     * @param int $lpId
13997
     *
13998
     * @return string
13999
     */
14000
    public static function getSelectedIcon($lpId)
14001
    {
14002
        $extraFieldValue = new ExtraFieldValue('lp');
14003
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
14004
        $icon = '';
14005
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
14006
            $icon = $lpIcon['value'];
14007
        }
14008
14009
        return $icon;
14010
    }
14011
14012
    public static function getSelectedIconHtml($lpId)
14013
    {
14014
        $icon = self::getSelectedIcon($lpId);
14015
14016
        if (empty($icon)) {
14017
            return '';
14018
        }
14019
14020
        $theme = api_get_visual_theme();
14021
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
14022
14023
        return Display::img($path);
14024
    }
14025
14026
    /**
14027
     * Get the depth level of LP item.
14028
     *
14029
     * @param array $items
14030
     * @param int   $currentItemId
14031
     *
14032
     * @return int
14033
     */
14034
    private static function get_level_for_item($items, $currentItemId)
14035
    {
14036
        $parentItemId = 0;
14037
        if (isset($items[$currentItemId])) {
14038
            $parentItemId = $items[$currentItemId]->parent;
14039
        }
14040
14041
        if ($parentItemId == 0) {
14042
            return 0;
14043
        } else {
14044
            return self::get_level_for_item($items, $parentItemId) + 1;
14045
        }
14046
    }
14047
14048
    /**
14049
     * Generate the link for a learnpath category as course tool.
14050
     *
14051
     * @param int $categoryId
14052
     *
14053
     * @return string
14054
     */
14055
    private static function getCategoryLinkForTool($categoryId)
14056
    {
14057
        $categoryId = (int) $categoryId;
14058
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14059
            .http_build_query(
14060
                [
14061
                    'action' => 'view_category',
14062
                    'id' => $categoryId,
14063
                ]
14064
            );
14065
14066
        return $link;
14067
    }
14068
14069
    /**
14070
     * Return the scorm item type object with spaces replaced with _
14071
     * The return result is use to build a css classname like scorm_type_$return.
14072
     *
14073
     * @param $in_type
14074
     *
14075
     * @return mixed
14076
     */
14077
    private static function format_scorm_type_item($in_type)
14078
    {
14079
        return str_replace(' ', '_', $in_type);
14080
    }
14081
14082
    /**
14083
     * Check and obtain the lp final item if exist.
14084
     *
14085
     * @return learnpathItem
14086
     */
14087
    private function getFinalItem()
14088
    {
14089
        if (empty($this->items)) {
14090
            return null;
14091
        }
14092
14093
        foreach ($this->items as $item) {
14094
            if ($item->type !== 'final_item') {
14095
                continue;
14096
            }
14097
14098
            return $item;
14099
        }
14100
    }
14101
14102
    /**
14103
     * Get the LP Final Item Template.
14104
     *
14105
     * @return string
14106
     */
14107
    private function getFinalItemTemplate()
14108
    {
14109
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14110
    }
14111
14112
    /**
14113
     * Get the LP Final Item Url.
14114
     *
14115
     * @return string
14116
     */
14117
    private function getSavedFinalItem()
14118
    {
14119
        $finalItem = $this->getFinalItem();
14120
        $doc = DocumentManager::get_document_data_by_id(
14121
            $finalItem->path,
14122
            $this->cc
14123
        );
14124
        if ($doc && file_exists($doc['absolute_path'])) {
14125
            return file_get_contents($doc['absolute_path']);
14126
        }
14127
14128
        return '';
14129
    }
14130
}
14131