Passed
Push — master ( 6f8cb0...cccadf )
by Julito
09:47
created

learnpath   F

Complexity

Total Complexity 1826

Size/Duplication

Total Lines 13554
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7492
dl 0
loc 13554
rs 0.8
c 5
b 1
f 0
wmc 1826

218 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\Framework\Container;
5
use Chamilo\CoreBundle\Repository\CourseRepository;
6
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
9
use Chamilo\CourseBundle\Entity\CItemProperty;
10
use Chamilo\CourseBundle\Entity\CLp;
11
use Chamilo\CourseBundle\Entity\CLpCategory;
12
use Chamilo\CourseBundle\Entity\CLpItem;
13
use Chamilo\CourseBundle\Entity\CLpItemView;
14
use Chamilo\CourseBundle\Entity\CTool;
15
use Chamilo\UserBundle\Entity\User;
16
use ChamiloSession as Session;
17
use Gedmo\Sortable\Entity\Repository\SortableRepository;
18
use Symfony\Component\Filesystem\Filesystem;
19
use Symfony\Component\Finder\Finder;
20
21
/**
22
 * Class learnpath
23
 * This class defines the parent attributes and methods for Chamilo learnpaths
24
 * and SCORM learnpaths. It is used by the scorm class.
25
 *
26
 * @todo decouple class
27
 *
28
 * @package chamilo.learnpath
29
 *
30
 * @author  Yannick Warnier <[email protected]>
31
 * @author  Julio Montoya   <[email protected]> Several improvements and fixes
32
 */
33
class learnpath
34
{
35
    public const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
37
    public $attempt = 0; // The number for the current ID view.
38
    public $cc; // Course (code) this learnpath is located in. @todo change name for something more comprensible ...
39
    public $current; // Id of the current item the user is viewing.
40
    public $current_score; // The score of the current item.
41
    public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet).
42
    public $current_time_stop; // The time the user closed this resource.
43
    public $default_status = 'not attempted';
44
    public $encoding = 'UTF-8';
45
    public $error = '';
46
    public $force_commit = false; // For SCORM only- if true will send a scorm LMSCommit() request on each LMSSetValue()
47
    public $index; // The index of the active learnpath_item in $ordered_items array.
48
    public $items = [];
49
    public $last; // item_id of last item viewed in the learning path.
50
    public $last_item_seen = 0; // In case we have already come in this lp, reuse the last item seen if authorized.
51
    public $license; // Which license this course has been given - not used yet on 20060522.
52
    public $lp_id; // DB iid for this learnpath.
53
    public $lp_view_id; // DB ID for lp_view
54
    public $maker; // Which maker has conceived the content (ENI, Articulate, ...).
55
    public $message = '';
56
    public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded).
57
    public $name; // Learnpath name (they generally have one).
58
    public $ordered_items = []; // List of the learnpath items in the order they are to be read.
59
    public $path = ''; // Path inside the scorm directory (if scorm).
60
    public $theme; // The current theme of the learning path.
61
    public $preview_image; // The current image of the learning path.
62
    public $accumulateScormTime; // Flag to decide whether to accumulate SCORM time or not
63
    public $accumulateWorkTime; // The min time of learnpath
64
65
    // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1).
66
    public $prevent_reinit = 1;
67
68
    // Describes the mode of progress bar display.
69
    public $seriousgame_mode = 0;
70
    public $progress_bar_mode = '%';
71
72
    // Percentage progress as saved in the db.
73
    public $progress_db = 0;
74
    public $proximity; // Wether the content is distant or local or unknown.
75
    public $refs_list = []; //list of items by ref => db_id. Used only for prerequisites match.
76
    // !!!This array (refs_list) is built differently depending on the nature of the LP.
77
    // If SCORM, uses ref, if Chamilo, uses id to keep a unique value.
78
    public $type; //type of learnpath. Could be 'chamilo', 'scorm', 'scorm2004', 'aicc', ...
79
    // TODO: Check if this type variable is useful here (instead of just in the controller script).
80
    public $user_id; //ID of the user that is viewing/using the course
81
    public $update_queue = [];
82
    public $scorm_debug = 0;
83
    public $arrMenu = []; // Array for the menu items.
84
    public $debug = 0; // Logging level.
85
    public $lp_session_id = 0;
86
    public $lp_view_session_id = 0; // The specific view might be bound to a session.
87
    public $prerequisite = 0;
88
    public $use_max_score = 1; // 1 or 0
89
    public $subscribeUsers = 0; // Subscribe users or not
90
    public $created_on = '';
91
    public $modified_on = '';
92
    public $publicated_on = '';
93
    public $expired_on = '';
94
    public $ref = null;
95
    public $course_int_id;
96
    public $course_info = [];
97
    public $categoryId;
98
99
    /**
100
     * Constructor.
101
     * Needs a database handler, a course code and a learnpath id from the database.
102
     * Also builds the list of items into $this->items.
103
     *
104
     * @param string $course  Course code
105
     * @param int    $lp_id   c_lp.iid
106
     * @param int    $user_id
107
     */
108
    public function __construct($course, $lp_id, $user_id)
109
    {
110
        $debug = $this->debug;
111
        $this->encoding = api_get_system_encoding();
112
        if ($debug) {
113
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
114
        }
115
        if (empty($course)) {
116
            $course = api_get_course_id();
117
        }
118
        $course_info = api_get_course_info($course);
119
        if (!empty($course_info)) {
120
            $this->cc = $course_info['code'];
121
            $this->course_info = $course_info;
122
            $course_id = $course_info['real_id'];
123
        } else {
124
            $this->error = 'Course code does not exist in database.';
125
        }
126
127
        $lp_id = (int) $lp_id;
128
        $course_id = (int) $course_id;
129
        $this->set_course_int_id($course_id);
130
        // Check learnpath ID.
131
        if (empty($lp_id) || empty($course_id)) {
132
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
133
        } else {
134
            // TODO: Make it flexible to use any course_code (still using env course code here).
135
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
136
            $sql = "SELECT * FROM $lp_table
137
                    WHERE iid = $lp_id";
138
            if ($debug) {
139
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
140
            }
141
            $res = Database::query($sql);
142
            if (Database::num_rows($res) > 0) {
143
                $this->lp_id = $lp_id;
144
                $row = Database::fetch_array($res);
145
                $this->type = $row['lp_type'];
146
                $this->name = stripslashes($row['name']);
147
                $this->proximity = $row['content_local'];
148
                $this->theme = $row['theme'];
149
                $this->maker = $row['content_maker'];
150
                $this->prevent_reinit = $row['prevent_reinit'];
151
                $this->seriousgame_mode = $row['seriousgame_mode'];
152
                $this->license = $row['content_license'];
153
                $this->scorm_debug = $row['debug'];
154
                $this->js_lib = $row['js_lib'];
155
                $this->path = $row['path'];
156
                $this->preview_image = $row['preview_image'];
157
                $this->author = $row['author'];
158
                $this->hide_toc_frame = $row['hide_toc_frame'];
159
                $this->lp_session_id = $row['session_id'];
160
                $this->use_max_score = $row['use_max_score'];
161
                $this->subscribeUsers = $row['subscribe_users'];
162
                $this->created_on = $row['created_on'];
163
                $this->modified_on = $row['modified_on'];
164
                $this->ref = $row['ref'];
165
                $this->categoryId = $row['category_id'];
166
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
167
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
168
169
                if (!empty($row['publicated_on'])) {
170
                    $this->publicated_on = $row['publicated_on'];
171
                }
172
173
                if (!empty($row['expired_on'])) {
174
                    $this->expired_on = $row['expired_on'];
175
                }
176
                if ($this->type == 2) {
177
                    if ($row['force_commit'] == 1) {
178
                        $this->force_commit = true;
179
                    }
180
                }
181
                $this->mode = $row['default_view_mod'];
182
183
                // Check user ID.
184
                if (empty($user_id)) {
185
                    $this->error = 'User ID is empty';
186
                } else {
187
                    $userInfo = api_get_user_info($user_id);
188
                    if (!empty($userInfo)) {
189
                        $this->user_id = $userInfo['user_id'];
190
                    } else {
191
                        $this->error = 'User ID does not exist in database #'.$user_id;
192
                    }
193
                }
194
195
                // End of variables checking.
196
                $session_id = api_get_session_id();
197
                //  Get the session condition for learning paths of the base + session.
198
                $session = api_get_session_condition($session_id);
199
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
200
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
201
202
                // Selecting by view_count descending allows to get the highest view_count first.
203
                $sql = "SELECT * FROM $lp_table
204
                        WHERE 
205
                            c_id = $course_id AND 
206
                            lp_id = $lp_id AND 
207
                            user_id = $user_id 
208
                            $session
209
                        ORDER BY view_count DESC";
210
                $res = Database::query($sql);
211
                if ($debug) {
212
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
213
                }
214
215
                if (Database::num_rows($res) > 0) {
216
                    if ($debug) {
217
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
218
                    }
219
                    $row = Database::fetch_array($res);
220
                    $this->attempt = $row['view_count'];
221
                    $this->lp_view_id = $row['id'];
222
                    $this->last_item_seen = $row['last_item'];
223
                    $this->progress_db = $row['progress'];
224
                    $this->lp_view_session_id = $row['session_id'];
225
                } elseif (!api_is_invitee()) {
226
                    if ($debug) {
227
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
228
                    }
229
                    $this->attempt = 1;
230
                    $params = [
231
                        'c_id' => $course_id,
232
                        'lp_id' => $lp_id,
233
                        'user_id' => $user_id,
234
                        'view_count' => 1,
235
                        'session_id' => $session_id,
236
                        'last_item' => 0,
237
                    ];
238
                    $this->last_item_seen = 0;
239
                    $this->lp_view_session_id = $session_id;
240
                    $this->lp_view_id = Database::insert($lp_table, $params);
241
                    if (!empty($this->lp_view_id)) {
242
                        $sql = "UPDATE $lp_table SET id = iid
243
                                WHERE iid = ".$this->lp_view_id;
244
                        Database::query($sql);
245
                    }
246
                }
247
248
                // Initialise items.
249
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
250
                $sql = "SELECT * FROM $lp_item_table
251
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
252
                        ORDER BY parent_item_id, display_order";
253
                $res = Database::query($sql);
254
255
                $lp_item_id_list = [];
256
                while ($row = Database::fetch_array($res)) {
257
                    $lp_item_id_list[] = $row['iid'];
258
                    switch ($this->type) {
259
                        case 3: //aicc
260
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
261
                            if (is_object($oItem)) {
262
                                $my_item_id = $oItem->get_id();
263
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
264
                                $oItem->set_prevent_reinit($this->prevent_reinit);
265
                                // Don't use reference here as the next loop will make the pointed object change.
266
                                $this->items[$my_item_id] = $oItem;
267
                                $this->refs_list[$oItem->ref] = $my_item_id;
268
                                if ($debug) {
269
                                    error_log(
270
                                        'learnpath::__construct() - '.
271
                                        'aicc object with id '.$my_item_id.
272
                                        ' set in items[]',
273
                                        0
274
                                    );
275
                                }
276
                            }
277
                            break;
278
                        case 2:
279
                            $oItem = new scormItem('db', $row['iid'], $course_id);
280
                            if (is_object($oItem)) {
281
                                $my_item_id = $oItem->get_id();
282
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
283
                                $oItem->set_prevent_reinit($this->prevent_reinit);
284
                                // Don't use reference here as the next loop will make the pointed object change.
285
                                $this->items[$my_item_id] = $oItem;
286
                                $this->refs_list[$oItem->ref] = $my_item_id;
287
                                if ($debug) {
288
                                    error_log('object with id '.$my_item_id.' set in items[]');
289
                                }
290
                            }
291
                            break;
292
                        case 1:
293
                        default:
294
                            if ($debug) {
295
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
296
                            }
297
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
298
299
                            if ($debug) {
300
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
301
                            }
302
                            if (is_object($oItem)) {
303
                                $my_item_id = $oItem->get_id();
304
                                // Moved down to when we are sure the item_view exists.
305
                                //$oItem->set_lp_view($this->lp_view_id);
306
                                $oItem->set_prevent_reinit($this->prevent_reinit);
307
                                // Don't use reference here as the next loop will make the pointed object change.
308
                                $this->items[$my_item_id] = $oItem;
309
                                $this->refs_list[$my_item_id] = $my_item_id;
310
                                if ($debug) {
311
                                    error_log(
312
                                        'learnpath::__construct() '.__LINE__.
313
                                        ' - object with id '.$my_item_id.' set in items[]'
314
                                    );
315
                                }
316
                            }
317
                            break;
318
                    }
319
320
                    // Setting the object level with variable $this->items[$i][parent]
321
                    foreach ($this->items as $itemLPObject) {
322
                        $level = self::get_level_for_item(
323
                            $this->items,
324
                            $itemLPObject->db_id
325
                        );
326
                        $itemLPObject->level = $level;
327
                    }
328
329
                    // Setting the view in the item object.
330
                    if (is_object($this->items[$row['iid']])) {
331
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
332
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
333
                            $this->items[$row['iid']]->current_start_time = 0;
334
                            $this->items[$row['iid']]->current_stop_time = 0;
335
                        }
336
                    }
337
                }
338
339
                if (!empty($lp_item_id_list)) {
340
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
341
                    if (!empty($lp_item_id_list_to_string)) {
342
                        // Get last viewing vars.
343
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
344
                        // This query should only return one or zero result.
345
                        $sql = "SELECT lp_item_id, status
346
                                FROM $itemViewTable
347
                                WHERE
348
                                    c_id = $course_id AND
349
                                    lp_view_id = ".$this->get_view_id()." AND
350
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
351
                                ORDER BY view_count DESC ";
352
                        $status_list = [];
353
                        $res = Database::query($sql);
354
                        while ($row = Database:: fetch_array($res)) {
355
                            $status_list[$row['lp_item_id']] = $row['status'];
356
                        }
357
358
                        foreach ($lp_item_id_list as $item_id) {
359
                            if (isset($status_list[$item_id])) {
360
                                $status = $status_list[$item_id];
361
                                if (is_object($this->items[$item_id])) {
362
                                    $this->items[$item_id]->set_status($status);
363
                                    if (empty($status)) {
364
                                        $this->items[$item_id]->set_status(
365
                                            $this->default_status
366
                                        );
367
                                    }
368
                                }
369
                            } else {
370
                                if (!api_is_invitee()) {
371
                                    if (is_object($this->items[$item_id])) {
372
                                        $this->items[$item_id]->set_status(
373
                                            $this->default_status
374
                                        );
375
                                    }
376
377
                                    if (!empty($this->lp_view_id)) {
378
                                        // Add that row to the lp_item_view table so that
379
                                        // we have something to show in the stats page.
380
                                        $params = [
381
                                            'c_id' => $course_id,
382
                                            'lp_item_id' => $item_id,
383
                                            'lp_view_id' => $this->lp_view_id,
384
                                            'view_count' => 1,
385
                                            'status' => 'not attempted',
386
                                            'start_time' => time(),
387
                                            'total_time' => 0,
388
                                            'score' => 0,
389
                                        ];
390
                                        $insertId = Database::insert($itemViewTable, $params);
391
392
                                        if ($insertId) {
393
                                            $sql = "UPDATE $itemViewTable SET id = iid
394
                                                    WHERE iid = $insertId";
395
                                            Database::query($sql);
396
                                        }
397
398
                                        $this->items[$item_id]->set_lp_view(
399
                                            $this->lp_view_id,
400
                                            $course_id
401
                                        );
402
                                    }
403
                                }
404
                            }
405
                        }
406
                    }
407
                }
408
409
                $this->ordered_items = self::get_flat_ordered_items_list(
410
                    $this->get_id(),
411
                    0,
412
                    $course_id
413
                );
414
                $this->max_ordered_items = 0;
415
                foreach ($this->ordered_items as $index => $dummy) {
416
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
417
                        $this->max_ordered_items = $index;
418
                    }
419
                }
420
                // TODO: Define the current item better.
421
                $this->first();
422
                if ($debug) {
423
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
424
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
425
                }
426
            } else {
427
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
428
            }
429
        }
430
    }
431
432
    /**
433
     * @return string
434
     */
435
    public function getCourseCode()
436
    {
437
        return $this->cc;
438
    }
439
440
    /**
441
     * @return int
442
     */
443
    public function get_course_int_id()
444
    {
445
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
446
    }
447
448
    /**
449
     * @param $course_id
450
     *
451
     * @return int
452
     */
453
    public function set_course_int_id($course_id)
454
    {
455
        return $this->course_int_id = (int) $course_id;
456
    }
457
458
    /**
459
     * Function rewritten based on old_add_item() from Yannick Warnier.
460
     * Due the fact that users can decide where the item should come, I had to overlook this function and
461
     * I found it better to rewrite it. Old function is still available.
462
     * Added also the possibility to add a description.
463
     *
464
     * @param int    $parent
465
     * @param int    $previous
466
     * @param string $type
467
     * @param int    $id               resource ID (ref)
468
     * @param string $title
469
     * @param string $description
470
     * @param int    $prerequisites
471
     * @param int    $max_time_allowed
472
     * @param int    $userId
473
     *
474
     * @return int
475
     */
476
    public function add_item(
477
        $parent,
478
        $previous,
479
        $type = 'dir',
480
        $id,
481
        $title,
482
        $description,
483
        $prerequisites = 0,
484
        $max_time_allowed = 0,
485
        $userId = 0
486
    ) {
487
        $course_id = $this->course_info['real_id'];
488
        if (empty($course_id)) {
489
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
490
            $this->course_info = api_get_course_info($this->cc);
491
            $course_id = $this->course_info['real_id'];
492
        }
493
        $userId = empty($userId) ? api_get_user_id() : $userId;
494
        $sessionId = api_get_session_id();
495
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
496
        $_course = $this->course_info;
497
        $parent = (int) $parent;
498
        $previous = (int) $previous;
499
        $id = (int) $id;
500
        $max_time_allowed = htmlentities($max_time_allowed);
501
        if (empty($max_time_allowed)) {
502
            $max_time_allowed = 0;
503
        }
504
        $sql = "SELECT COUNT(iid) AS num
505
                FROM $tbl_lp_item
506
                WHERE
507
                    c_id = $course_id AND
508
                    lp_id = ".$this->get_id()." AND
509
                    parent_item_id = $parent ";
510
511
        $res_count = Database::query($sql);
512
        $row = Database::fetch_array($res_count);
513
        $num = $row['num'];
514
515
        $tmp_previous = 0;
516
        $display_order = 0;
517
        $next = 0;
518
        if ($num > 0) {
519
            if (empty($previous)) {
520
                $sql = "SELECT iid, next_item_id, display_order
521
                        FROM $tbl_lp_item
522
                        WHERE
523
                            c_id = $course_id AND
524
                            lp_id = ".$this->get_id()." AND
525
                            parent_item_id = $parent AND
526
                            previous_item_id = 0 OR
527
                            previous_item_id = $parent";
528
                $result = Database::query($sql);
529
                $row = Database::fetch_array($result);
530
                if ($row) {
531
                    $next = $row['iid'];
532
                }
533
            } else {
534
                $previous = (int) $previous;
535
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
536
						FROM $tbl_lp_item
537
                        WHERE
538
                            c_id = $course_id AND
539
                            lp_id = ".$this->get_id()." AND
540
                            id = $previous";
541
                $result = Database::query($sql);
542
                $row = Database::fetch_array($result);
543
                if ($row) {
544
                    $tmp_previous = $row['iid'];
545
                    $next = $row['next_item_id'];
546
                    $display_order = $row['display_order'];
547
                }
548
            }
549
        }
550
551
        $id = (int) $id;
552
        $typeCleaned = Database::escape_string($type);
553
        $max_score = 100;
554
        if ($type === 'quiz') {
555
            $sql = 'SELECT SUM(ponderation)
556
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
557
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
558
                    ON
559
                        quiz_question.id = quiz_rel_question.question_id AND
560
                        quiz_question.c_id = quiz_rel_question.c_id
561
                    WHERE
562
                        quiz_rel_question.exercice_id = '.$id." AND
563
                        quiz_question.c_id = $course_id AND
564
                        quiz_rel_question.c_id = $course_id ";
565
            $rsQuiz = Database::query($sql);
566
            $max_score = Database::result($rsQuiz, 0, 0);
567
568
            // Disabling the exercise if we add it inside a LP
569
            $exercise = new Exercise($course_id);
570
            $exercise->read($id);
571
            $exercise->disable();
572
            $exercise->save();
573
        }
574
575
        $params = [
576
            'c_id' => $course_id,
577
            'lp_id' => $this->get_id(),
578
            'item_type' => $typeCleaned,
579
            'ref' => '',
580
            'title' => $title,
581
            'description' => $description,
582
            'path' => $id,
583
            'max_score' => $max_score,
584
            'parent_item_id' => $parent,
585
            'previous_item_id' => $previous,
586
            'next_item_id' => (int) $next,
587
            'display_order' => $display_order + 1,
588
            'prerequisite' => $prerequisites,
589
            'max_time_allowed' => $max_time_allowed,
590
            'min_score' => 0,
591
            'launch_data' => '',
592
        ];
593
594
        if ($prerequisites != 0) {
595
            $params['prerequisite'] = $prerequisites;
596
        }
597
598
        $new_item_id = Database::insert($tbl_lp_item, $params);
599
        if ($new_item_id) {
600
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
601
            Database::query($sql);
602
603
            if (!empty($next)) {
604
                $sql = "UPDATE $tbl_lp_item
605
                        SET previous_item_id = $new_item_id 
606
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
607
                Database::query($sql);
608
            }
609
610
            // Update the item that should be before the new item.
611
            if (!empty($tmp_previous)) {
612
                $sql = "UPDATE $tbl_lp_item
613
                        SET next_item_id = $new_item_id
614
                        WHERE c_id = $course_id AND id = $tmp_previous";
615
                Database::query($sql);
616
            }
617
618
            // Update all the items after the new item.
619
            $sql = "UPDATE $tbl_lp_item
620
                        SET display_order = display_order + 1
621
                    WHERE
622
                        c_id = $course_id AND
623
                        lp_id = ".$this->get_id()." AND
624
                        iid <> $new_item_id AND
625
                        parent_item_id = $parent AND
626
                        display_order > $display_order";
627
            Database::query($sql);
628
629
            // Update the item that should come after the new item.
630
            $sql = "UPDATE $tbl_lp_item
631
                    SET ref = $new_item_id
632
                    WHERE c_id = $course_id AND iid = $new_item_id";
633
            Database::query($sql);
634
635
            $sql = "UPDATE $tbl_lp_item
636
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
637
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
638
            Database::query($sql);
639
640
            // Upload audio.
641
            if (!empty($_FILES['mp3']['name'])) {
642
                // Create the audio folder if it does not exist yet.
643
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
644
                if (!is_dir($filepath.'audio')) {
645
                    mkdir(
646
                        $filepath.'audio',
647
                        api_get_permissions_for_new_directories()
648
                    );
649
                    $audio_id = DocumentManager::addDocument(
650
                        $_course,
651
                        '/audio',
652
                        'folder',
653
                        0,
654
                        'audio',
655
                        '',
656
                        0,
657
                        true,
658
                        null,
659
                        $sessionId,
660
                        $userId
661
                    );
662
                }
663
664
                $file_path = handle_uploaded_document(
665
                    $_course,
666
                    $_FILES['mp3'],
667
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
668
                    '/audio',
669
                    $userId,
670
                    '',
671
                    '',
672
                    '',
673
                    '',
674
                    false
675
                );
676
677
                // Getting the filename only.
678
                $file_components = explode('/', $file_path);
679
                $file = $file_components[count($file_components) - 1];
680
681
                // Store the mp3 file in the lp_item table.
682
                $sql = "UPDATE $tbl_lp_item SET
683
                          audio = '".Database::escape_string($file)."'
684
                        WHERE iid = '".intval($new_item_id)."'";
685
                Database::query($sql);
686
            }
687
        }
688
689
        return $new_item_id;
690
    }
691
692
    /**
693
     * Static admin function allowing addition of a learnpath to a course.
694
     *
695
     * @param string $courseCode
696
     * @param string $name
697
     * @param string $description
698
     * @param string $learnpath
699
     * @param string $origin
700
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
701
     * @param string $publicated_on
702
     * @param string $expired_on
703
     * @param int    $categoryId
704
     * @param int    $userId
705
     *
706
     * @return int The new learnpath ID on success, 0 on failure
707
     */
708
    public static function add_lp(
709
        $courseCode,
710
        $name,
711
        $description = '',
712
        $learnpath = 'guess',
713
        $origin = 'zip',
714
        $zipname = '',
715
        $publicated_on = '',
716
        $expired_on = '',
717
        $categoryId = 0,
718
        $userId = 0
719
    ) {
720
        global $charset;
721
722
        if (!empty($courseCode)) {
723
            $courseInfo = api_get_course_info($courseCode);
724
            $course_id = $courseInfo['real_id'];
725
        } else {
726
            $course_id = api_get_course_int_id();
727
            $courseInfo = api_get_course_info();
728
        }
729
730
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
731
        // Check course code exists.
732
        // Check lp_name doesn't exist, otherwise append something.
733
        $i = 0;
734
        $categoryId = (int) $categoryId;
735
        // Session id.
736
        $session_id = api_get_session_id();
737
        $userId = empty($userId) ? api_get_user_id() : $userId;
738
739
        if (empty($publicated_on)) {
740
            $publicated_on = null;
741
        } else {
742
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
743
        }
744
745
        if (empty($expired_on)) {
746
            $expired_on = null;
747
        } else {
748
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
749
        }
750
751
        $check_name = "SELECT * FROM $tbl_lp
752
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
753
        $res_name = Database::query($check_name);
754
755
        while (Database::num_rows($res_name)) {
756
            // There is already one such name, update the current one a bit.
757
            $i++;
758
            $name = $name.' - '.$i;
759
            $check_name = "SELECT * FROM $tbl_lp 
760
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
761
            $res_name = Database::query($check_name);
762
        }
763
        // New name does not exist yet; keep it.
764
        // Escape description.
765
        // Kevin: added htmlentities().
766
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
767
        $type = 1;
768
        switch ($learnpath) {
769
            case 'guess':
770
                break;
771
            case 'dokeos':
772
            case 'chamilo':
773
                $type = 1;
774
                break;
775
            case 'aicc':
776
                break;
777
        }
778
779
        switch ($origin) {
780
            case 'zip':
781
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
782
                break;
783
            case 'manual':
784
            default:
785
                $get_max = "SELECT MAX(display_order) 
786
                            FROM $tbl_lp WHERE c_id = $course_id";
787
                $res_max = Database::query($get_max);
788
                if (Database::num_rows($res_max) < 1) {
789
                    $dsp = 1;
790
                } else {
791
                    $row = Database::fetch_array($res_max);
792
                    $dsp = $row[0] + 1;
793
                }
794
795
                $params = [
796
                    'c_id' => $course_id,
797
                    'lp_type' => $type,
798
                    'name' => $name,
799
                    'description' => $description,
800
                    'path' => '',
801
                    'default_view_mod' => 'embedded',
802
                    'default_encoding' => 'UTF-8',
803
                    'display_order' => $dsp,
804
                    'content_maker' => 'Chamilo',
805
                    'content_local' => 'local',
806
                    'js_lib' => '',
807
                    'session_id' => $session_id,
808
                    'created_on' => api_get_utc_datetime(),
809
                    'modified_on' => api_get_utc_datetime(),
810
                    'publicated_on' => $publicated_on,
811
                    'expired_on' => $expired_on,
812
                    'category_id' => $categoryId,
813
                    'force_commit' => 0,
814
                    'content_license' => '',
815
                    'debug' => 0,
816
                    'theme' => '',
817
                    'preview_image' => '',
818
                    'author' => '',
819
                    'prerequisite' => 0,
820
                    'hide_toc_frame' => 0,
821
                    'seriousgame_mode' => 0,
822
                    'autolaunch' => 0,
823
                    'max_attempts' => 0,
824
                    'subscribe_users' => 0,
825
                    'accumulate_scorm_time' => 1,
826
                ];
827
                $id = Database::insert($tbl_lp, $params);
828
829
                if ($id > 0) {
830
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
831
                    Database::query($sql);
832
833
                    // Insert into item_property.
834
                    api_item_property_update(
835
                        $courseInfo,
836
                        TOOL_LEARNPATH,
837
                        $id,
838
                        'LearnpathAdded',
839
                        $userId
840
                    );
841
                    api_set_default_visibility(
842
                        $id,
843
                        TOOL_LEARNPATH,
844
                        0,
845
                        $courseInfo,
846
                        $session_id,
847
                        $userId
848
                    );
849
850
                    return $id;
851
                }
852
                break;
853
        }
854
    }
855
856
    /**
857
     * Auto completes the parents of an item in case it's been completed or passed.
858
     *
859
     * @param int $item Optional ID of the item from which to look for parents
860
     */
861
    public function autocomplete_parents($item)
862
    {
863
        $debug = $this->debug;
864
865
        if (empty($item)) {
866
            $item = $this->current;
867
        }
868
869
        $currentItem = $this->getItem($item);
870
        if ($currentItem) {
871
            $parent_id = $currentItem->get_parent();
872
            $parent = $this->getItem($parent_id);
873
            if ($parent) {
874
                // if $item points to an object and there is a parent.
875
                if ($debug) {
876
                    error_log(
877
                        'Autocompleting parent of item '.$item.' '.
878
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
879
                        0
880
                    );
881
                }
882
883
                // New experiment including failed and browsed in completed status.
884
                //$current_status = $currentItem->get_status();
885
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
886
                // Fixes chapter auto complete
887
                if (true) {
888
                    // If the current item is completed or passes or succeeded.
889
                    $updateParentStatus = true;
890
                    if ($debug) {
891
                        error_log('Status of current item is alright');
892
                    }
893
894
                    foreach ($parent->get_children() as $childItemId) {
895
                        $childItem = $this->getItem($childItemId);
896
897
                        // If children was not set try to get the info
898
                        if (empty($childItem->db_item_view_id)) {
899
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
900
                        }
901
902
                        // Check all his brothers (parent's children) for completion status.
903
                        if ($childItemId != $item) {
904
                            if ($debug) {
905
                                error_log(
906
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
907
                                    0
908
                                );
909
                            }
910
                            // Trying completing parents of failed and browsed items as well.
911
                            if ($childItem->status_is(
912
                                [
913
                                    'completed',
914
                                    'passed',
915
                                    'succeeded',
916
                                    'browsed',
917
                                    'failed',
918
                                ]
919
                            )
920
                            ) {
921
                                // Keep completion status to true.
922
                                continue;
923
                            } else {
924
                                if ($debug > 2) {
925
                                    error_log(
926
                                        '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,
927
                                        0
928
                                    );
929
                                }
930
                                $updateParentStatus = false;
931
                                break;
932
                            }
933
                        }
934
                    }
935
936
                    if ($updateParentStatus) {
937
                        // If all the children were completed:
938
                        $parent->set_status('completed');
939
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
940
                        // Force the status to "completed"
941
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
942
                        $this->update_queue[$parent->get_id()] = 'completed';
943
                        if ($debug) {
944
                            error_log(
945
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
946
                                print_r($this->update_queue, 1),
947
                                0
948
                            );
949
                        }
950
                        // Recursive call.
951
                        $this->autocomplete_parents($parent->get_id());
952
                    }
953
                }
954
            } else {
955
                if ($debug) {
956
                    error_log("Parent #$parent_id does not exists");
957
                }
958
            }
959
        } else {
960
            if ($debug) {
961
                error_log("#$item is an item that doesn't have parents");
962
            }
963
        }
964
    }
965
966
    /**
967
     * Closes the current resource.
968
     *
969
     * Stops the timer
970
     * Saves into the database if required
971
     * Clears the current resource data from this object
972
     *
973
     * @return bool True on success, false on failure
974
     */
975
    public function close()
976
    {
977
        if (empty($this->lp_id)) {
978
            $this->error = 'Trying to close this learnpath but no ID is set';
979
980
            return false;
981
        }
982
        $this->current_time_stop = time();
983
        $this->ordered_items = [];
984
        $this->index = 0;
985
        unset($this->lp_id);
986
        //unset other stuff
987
        return true;
988
    }
989
990
    /**
991
     * Static admin function allowing removal of a learnpath.
992
     *
993
     * @param array  $courseInfo
994
     * @param int    $id         Learnpath ID
995
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
996
     *
997
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
998
     */
999
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1000
    {
1001
        $course_id = api_get_course_int_id();
1002
        if (!empty($courseInfo)) {
1003
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1004
        }
1005
1006
        // TODO: Implement a way of getting this to work when the current object is not set.
1007
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1008
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1009
        if (!empty($id) && ($id != $this->lp_id)) {
1010
            return false;
1011
        }
1012
1013
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1014
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1015
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1016
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1017
1018
        // Delete lp item id.
1019
        foreach ($this->items as $lpItemId => $dummy) {
1020
            $sql = "DELETE FROM $lp_item_view
1021
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1022
            Database::query($sql);
1023
        }
1024
1025
        // Proposed by Christophe (nickname: clefevre)
1026
        $sql = "DELETE FROM $lp_item
1027
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1028
        Database::query($sql);
1029
1030
        $sql = "DELETE FROM $lp_view 
1031
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1032
        Database::query($sql);
1033
1034
        self::toggle_publish($this->lp_id, 'i');
1035
1036
        if ($this->type == 2 || $this->type == 3) {
1037
            // This is a scorm learning path, delete the files as well.
1038
            $sql = "SELECT path FROM $lp
1039
                    WHERE iid = ".$this->lp_id;
1040
            $res = Database::query($sql);
1041
            if (Database::num_rows($res) > 0) {
1042
                $row = Database::fetch_array($res);
1043
                $path = $row['path'];
1044
                $sql = "SELECT id FROM $lp
1045
                        WHERE 
1046
                            c_id = $course_id AND
1047
                            path = '$path' AND 
1048
                            iid != ".$this->lp_id;
1049
                $res = Database::query($sql);
1050
                if (Database::num_rows($res) > 0) {
1051
                    // Another learning path uses this directory, so don't delete it.
1052
                    if ($this->debug > 2) {
1053
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1054
                    }
1055
                } else {
1056
                    // No other LP uses that directory, delete it.
1057
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1058
                    // The absolute system path for this course.
1059
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1060
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1061
                        if ($this->debug > 2) {
1062
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1063
                        }
1064
                        // Proposed by Christophe (clefevre).
1065
                        if (strcmp(substr($path, -2), "/.") == 0) {
1066
                            $path = substr($path, 0, -1); // Remove "." at the end.
1067
                        }
1068
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1069
                        rmdirr($course_scorm_dir.$path);
1070
                    }
1071
                }
1072
            }
1073
        }
1074
1075
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1076
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1077
        // Delete tools
1078
        $sql = "DELETE FROM $tbl_tool
1079
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1080
        Database::query($sql);
1081
1082
        $sql = "DELETE FROM $lp 
1083
                WHERE iid = ".$this->lp_id;
1084
        Database::query($sql);
1085
        // Updates the display order of all lps.
1086
        $this->update_display_order();
1087
1088
        api_item_property_update(
1089
            api_get_course_info(),
1090
            TOOL_LEARNPATH,
1091
            $this->lp_id,
1092
            'delete',
1093
            api_get_user_id()
1094
        );
1095
1096
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1097
            api_get_course_id(),
1098
            4,
1099
            $id,
1100
            api_get_session_id()
1101
        );
1102
1103
        if ($link_info !== false) {
1104
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1105
        }
1106
1107
        if (api_get_setting('search_enabled') == 'true') {
1108
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1109
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1110
        }
1111
    }
1112
1113
    /**
1114
     * Removes all the children of one item - dangerous!
1115
     *
1116
     * @param int $id Element ID of which children have to be removed
1117
     *
1118
     * @return int Total number of children removed
1119
     */
1120
    public function delete_children_items($id)
1121
    {
1122
        $course_id = $this->course_info['real_id'];
1123
1124
        $num = 0;
1125
        $id = (int) $id;
1126
        if (empty($id) || empty($course_id)) {
1127
            return false;
1128
        }
1129
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1130
        $sql = "SELECT * FROM $lp_item 
1131
                WHERE c_id = $course_id AND parent_item_id = $id";
1132
        $res = Database::query($sql);
1133
        while ($row = Database::fetch_array($res)) {
1134
            $num += $this->delete_children_items($row['iid']);
1135
            $sql = "DELETE FROM $lp_item 
1136
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1137
            Database::query($sql);
1138
            $num++;
1139
        }
1140
1141
        return $num;
1142
    }
1143
1144
    /**
1145
     * Removes an item from the current learnpath.
1146
     *
1147
     * @param int $id Elem ID (0 if first)
1148
     *
1149
     * @return int Number of elements moved
1150
     *
1151
     * @todo implement resource removal
1152
     */
1153
    public function delete_item($id)
1154
    {
1155
        $course_id = api_get_course_int_id();
1156
        $id = (int) $id;
1157
        // TODO: Implement the resource removal.
1158
        if (empty($id) || empty($course_id)) {
1159
            return false;
1160
        }
1161
        // First select item to get previous, next, and display order.
1162
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1163
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1164
        $res_sel = Database::query($sql_sel);
1165
        if (Database::num_rows($res_sel) < 1) {
1166
            return false;
1167
        }
1168
        $row = Database::fetch_array($res_sel);
1169
        $previous = $row['previous_item_id'];
1170
        $next = $row['next_item_id'];
1171
        $display = $row['display_order'];
1172
        $parent = $row['parent_item_id'];
1173
        $lp = $row['lp_id'];
1174
        // Delete children items.
1175
        $this->delete_children_items($id);
1176
        // Now delete the item.
1177
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1178
        Database::query($sql_del);
1179
        // Now update surrounding items.
1180
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1181
                    WHERE iid = $previous";
1182
        Database::query($sql_upd);
1183
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1184
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1185
        Database::query($sql_upd);
1186
        // Now update all following items with new display order.
1187
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1188
                    WHERE 
1189
                        c_id = $course_id AND 
1190
                        lp_id = $lp AND 
1191
                        parent_item_id = $parent AND 
1192
                        display_order > $display";
1193
        Database::query($sql_all);
1194
1195
        //Removing prerequisites since the item will not longer exist
1196
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1197
                    WHERE c_id = $course_id AND prerequisite = $id";
1198
        Database::query($sql_all);
1199
1200
        $sql = "UPDATE $lp_item
1201
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1202
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1203
        Database::query($sql);
1204
1205
        // Remove from search engine if enabled.
1206
        if (api_get_setting('search_enabled') === 'true') {
1207
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1208
            $sql = 'SELECT * FROM %s 
1209
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1210
                    LIMIT 1';
1211
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1212
            $res = Database::query($sql);
1213
            if (Database::num_rows($res) > 0) {
1214
                $row2 = Database::fetch_array($res);
1215
                $di = new ChamiloIndexer();
1216
                $di->remove_document($row2['search_did']);
1217
            }
1218
            $sql = 'DELETE FROM %s 
1219
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1220
                    LIMIT 1';
1221
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1222
            Database::query($sql);
1223
        }
1224
    }
1225
1226
    /**
1227
     * Updates an item's content in place.
1228
     *
1229
     * @param int    $id               Element ID
1230
     * @param int    $parent           Parent item ID
1231
     * @param int    $previous         Previous item ID
1232
     * @param string $title            Item title
1233
     * @param string $description      Item description
1234
     * @param string $prerequisites    Prerequisites (optional)
1235
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1236
     * @param int    $max_time_allowed
1237
     * @param string $url
1238
     *
1239
     * @return bool True on success, false on error
1240
     */
1241
    public function edit_item(
1242
        $id,
1243
        $parent,
1244
        $previous,
1245
        $title,
1246
        $description,
1247
        $prerequisites = '0',
1248
        $audio = [],
1249
        $max_time_allowed = 0,
1250
        $url = ''
1251
    ) {
1252
        $course_id = api_get_course_int_id();
1253
        $_course = api_get_course_info();
1254
        $id = (int) $id;
1255
1256
        if (empty($max_time_allowed)) {
1257
            $max_time_allowed = 0;
1258
        }
1259
1260
        if (empty($id) || empty($_course)) {
1261
            return false;
1262
        }
1263
1264
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1265
        $sql = "SELECT * FROM $tbl_lp_item 
1266
                WHERE iid = $id";
1267
        $res_select = Database::query($sql);
1268
        $row_select = Database::fetch_array($res_select);
1269
        $audio_update_sql = '';
1270
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1271
            // Create the audio folder if it does not exist yet.
1272
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1273
            if (!is_dir($filepath.'audio')) {
1274
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1275
                $audio_id = DocumentManager::addDocument(
1276
                    $_course,
1277
                    '/audio',
1278
                    'folder',
1279
                    0,
1280
                    'audio'
1281
                );
1282
            }
1283
1284
            // Upload file in documents.
1285
            $pi = pathinfo($audio['name']);
1286
            if ($pi['extension'] === 'mp3') {
1287
                $c_det = api_get_course_info($this->cc);
1288
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1289
                $path = handle_uploaded_document(
1290
                    $c_det,
1291
                    $audio,
1292
                    $bp,
1293
                    '/audio',
1294
                    api_get_user_id(),
1295
                    0,
1296
                    null,
1297
                    0,
1298
                    'rename',
1299
                    false,
1300
                    0
1301
                );
1302
                $path = substr($path, 7);
1303
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1304
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1305
            }
1306
        }
1307
1308
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1309
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1310
1311
        // TODO: htmlspecialchars to be checked for encoding related problems.
1312
        if ($same_parent && $same_previous) {
1313
            // Only update title and description.
1314
            $sql = "UPDATE $tbl_lp_item
1315
                    SET title = '".Database::escape_string($title)."',
1316
                        prerequisite = '".$prerequisites."',
1317
                        description = '".Database::escape_string($description)."'
1318
                        ".$audio_update_sql.",
1319
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1320
                    WHERE iid = $id";
1321
            Database::query($sql);
1322
        } else {
1323
            $old_parent = $row_select['parent_item_id'];
1324
            $old_previous = $row_select['previous_item_id'];
1325
            $old_next = $row_select['next_item_id'];
1326
            $old_order = $row_select['display_order'];
1327
            $old_prerequisite = $row_select['prerequisite'];
1328
            $old_max_time_allowed = $row_select['max_time_allowed'];
1329
1330
            /* BEGIN -- virtually remove the current item id */
1331
            /* for the next and previous item it is like the current item doesn't exist anymore */
1332
            if ($old_previous != 0) {
1333
                // Next
1334
                $sql = "UPDATE $tbl_lp_item
1335
                        SET next_item_id = $old_next
1336
                        WHERE iid = $old_previous";
1337
                Database::query($sql);
1338
            }
1339
1340
            if (!empty($old_next)) {
1341
                // Previous
1342
                $sql = "UPDATE $tbl_lp_item
1343
                        SET previous_item_id = $old_previous
1344
                        WHERE iid = $old_next";
1345
                Database::query($sql);
1346
            }
1347
1348
            // display_order - 1 for every item with a display_order
1349
            // bigger then the display_order of the current item.
1350
            $sql = "UPDATE $tbl_lp_item
1351
                    SET display_order = display_order - 1
1352
                    WHERE
1353
                        c_id = $course_id AND
1354
                        display_order > $old_order AND
1355
                        lp_id = ".$this->lp_id." AND
1356
                        parent_item_id = $old_parent";
1357
            Database::query($sql);
1358
            /* END -- virtually remove the current item id */
1359
1360
            /* BEGIN -- update the current item id to his new location */
1361
            if ($previous == 0) {
1362
                // Select the data of the item that should come after the current item.
1363
                $sql = "SELECT id, display_order
1364
                        FROM $tbl_lp_item
1365
                        WHERE
1366
                            c_id = $course_id AND
1367
                            lp_id = ".$this->lp_id." AND
1368
                            parent_item_id = $parent AND
1369
                            previous_item_id = $previous";
1370
                $res_select_old = Database::query($sql);
1371
                $row_select_old = Database::fetch_array($res_select_old);
1372
1373
                // If the new parent didn't have children before.
1374
                if (Database::num_rows($res_select_old) == 0) {
1375
                    $new_next = 0;
1376
                    $new_order = 1;
1377
                } else {
1378
                    $new_next = $row_select_old['id'];
1379
                    $new_order = $row_select_old['display_order'];
1380
                }
1381
            } else {
1382
                // Select the data of the item that should come before the current item.
1383
                $sql = "SELECT next_item_id, display_order
1384
                        FROM $tbl_lp_item
1385
                        WHERE iid = $previous";
1386
                $res_select_old = Database::query($sql);
1387
                $row_select_old = Database::fetch_array($res_select_old);
1388
                $new_next = $row_select_old['next_item_id'];
1389
                $new_order = $row_select_old['display_order'] + 1;
1390
            }
1391
1392
            // TODO: htmlspecialchars to be checked for encoding related problems.
1393
            // Update the current item with the new data.
1394
            $sql = "UPDATE $tbl_lp_item
1395
                    SET
1396
                        title = '".Database::escape_string($title)."',
1397
                        description = '".Database::escape_string($description)."',
1398
                        parent_item_id = $parent,
1399
                        previous_item_id = $previous,
1400
                        next_item_id = $new_next,
1401
                        display_order = $new_order
1402
                        $audio_update_sql
1403
                    WHERE iid = $id";
1404
            Database::query($sql);
1405
1406
            if ($previous != 0) {
1407
                // Update the previous item's next_item_id.
1408
                $sql = "UPDATE $tbl_lp_item
1409
                        SET next_item_id = $id
1410
                        WHERE iid = $previous";
1411
                Database::query($sql);
1412
            }
1413
1414
            if (!empty($new_next)) {
1415
                // Update the next item's previous_item_id.
1416
                $sql = "UPDATE $tbl_lp_item
1417
                        SET previous_item_id = $id
1418
                        WHERE iid = $new_next";
1419
                Database::query($sql);
1420
            }
1421
1422
            if ($old_prerequisite != $prerequisites) {
1423
                $sql = "UPDATE $tbl_lp_item
1424
                        SET prerequisite = '$prerequisites'
1425
                        WHERE iid = $id";
1426
                Database::query($sql);
1427
            }
1428
1429
            if ($old_max_time_allowed != $max_time_allowed) {
1430
                // update max time allowed
1431
                $sql = "UPDATE $tbl_lp_item
1432
                        SET max_time_allowed = $max_time_allowed
1433
                        WHERE iid = $id";
1434
                Database::query($sql);
1435
            }
1436
1437
            // Update all the items with the same or a bigger display_order than the current item.
1438
            $sql = "UPDATE $tbl_lp_item
1439
                    SET display_order = display_order + 1
1440
                    WHERE
1441
                       c_id = $course_id AND
1442
                       lp_id = ".$this->get_id()." AND
1443
                       iid <> $id AND
1444
                       parent_item_id = $parent AND
1445
                       display_order >= $new_order";
1446
            Database::query($sql);
1447
        }
1448
1449
        if ($row_select['item_type'] == 'link') {
1450
            $link = new Link();
1451
            $linkId = $row_select['path'];
1452
            $link->updateLink($linkId, $url);
1453
        }
1454
    }
1455
1456
    /**
1457
     * Updates an item's prereq in place.
1458
     *
1459
     * @param int    $id              Element ID
1460
     * @param string $prerequisite_id Prerequisite Element ID
1461
     * @param int    $minScore        Prerequisite min score
1462
     * @param int    $maxScore        Prerequisite max score
1463
     *
1464
     * @return bool True on success, false on error
1465
     */
1466
    public function edit_item_prereq(
1467
        $id,
1468
        $prerequisite_id,
1469
        $minScore = 0,
1470
        $maxScore = 100
1471
    ) {
1472
        $id = (int) $id;
1473
        $prerequisite_id = (int) $prerequisite_id;
1474
1475
        if (empty($id)) {
1476
            return false;
1477
        }
1478
1479
        if (empty($minScore) || $minScore < 0) {
1480
            $minScore = 0;
1481
        }
1482
1483
        if (empty($maxScore) || $maxScore < 0) {
1484
            $maxScore = 100;
1485
        }
1486
1487
        $minScore = floatval($minScore);
1488
        $maxScore = floatval($maxScore);
1489
1490
        if (empty($prerequisite_id)) {
1491
            $prerequisite_id = 'NULL';
1492
            $minScore = 0;
1493
            $maxScore = 100;
1494
        }
1495
1496
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1497
        $sql = " UPDATE $tbl_lp_item
1498
                 SET
1499
                    prerequisite = $prerequisite_id ,
1500
                    prerequisite_min_score = $minScore ,
1501
                    prerequisite_max_score = $maxScore
1502
                 WHERE iid = $id";
1503
1504
        Database::query($sql);
1505
1506
        return true;
1507
    }
1508
1509
    /**
1510
     * Get the specific prefix index terms of this learning path.
1511
     *
1512
     * @param string $prefix
1513
     *
1514
     * @return array Array of terms
1515
     */
1516
    public function get_common_index_terms_by_prefix($prefix)
1517
    {
1518
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1519
        $terms = get_specific_field_values_list_by_prefix(
1520
            $prefix,
1521
            $this->cc,
1522
            TOOL_LEARNPATH,
1523
            $this->lp_id
1524
        );
1525
        $prefix_terms = [];
1526
        if (!empty($terms)) {
1527
            foreach ($terms as $term) {
1528
                $prefix_terms[] = $term['value'];
1529
            }
1530
        }
1531
1532
        return $prefix_terms;
1533
    }
1534
1535
    /**
1536
     * Gets the number of items currently completed.
1537
     *
1538
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1539
     *
1540
     * @return int The number of items currently completed
1541
     */
1542
    public function get_complete_items_count($failedStatusException = false)
1543
    {
1544
        $i = 0;
1545
        $completedStatusList = [
1546
            'completed',
1547
            'passed',
1548
            'succeeded',
1549
            'browsed',
1550
        ];
1551
1552
        if (!$failedStatusException) {
1553
            $completedStatusList[] = 'failed';
1554
        }
1555
1556
        foreach ($this->items as $id => $dummy) {
1557
            // Trying failed and browsed considered "progressed" as well.
1558
            if ($this->items[$id]->status_is($completedStatusList) &&
1559
                $this->items[$id]->get_type() != 'dir'
1560
            ) {
1561
                $i++;
1562
            }
1563
        }
1564
1565
        return $i;
1566
    }
1567
1568
    /**
1569
     * Gets the current item ID.
1570
     *
1571
     * @return int The current learnpath item id
1572
     */
1573
    public function get_current_item_id()
1574
    {
1575
        $current = 0;
1576
        if (!empty($this->current)) {
1577
            $current = (int) $this->current;
1578
        }
1579
1580
        return $current;
1581
    }
1582
1583
    /**
1584
     * Force to get the first learnpath item id.
1585
     *
1586
     * @return int The current learnpath item id
1587
     */
1588
    public function get_first_item_id()
1589
    {
1590
        $current = 0;
1591
        if (is_array($this->ordered_items)) {
1592
            $current = $this->ordered_items[0];
1593
        }
1594
1595
        return $current;
1596
    }
1597
1598
    /**
1599
     * Gets the total number of items available for viewing in this SCORM.
1600
     *
1601
     * @return int The total number of items
1602
     */
1603
    public function get_total_items_count()
1604
    {
1605
        return count($this->items);
1606
    }
1607
1608
    /**
1609
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1610
     *
1611
     * @return int The total no-chapters number of items
1612
     */
1613
    public function getTotalItemsCountWithoutDirs()
1614
    {
1615
        $total = 0;
1616
        $typeListNotToCount = self::getChapterTypes();
1617
        foreach ($this->items as $temp2) {
1618
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1619
                $total++;
1620
            }
1621
        }
1622
1623
        return $total;
1624
    }
1625
1626
    /**
1627
     *  Sets the first element URL.
1628
     */
1629
    public function first()
1630
    {
1631
        if ($this->debug > 0) {
1632
            error_log('In learnpath::first()', 0);
1633
            error_log('$this->last_item_seen '.$this->last_item_seen);
1634
        }
1635
1636
        // Test if the last_item_seen exists and is not a dir.
1637
        if (count($this->ordered_items) == 0) {
1638
            $this->index = 0;
1639
        }
1640
1641
        if (!empty($this->last_item_seen) &&
1642
            !empty($this->items[$this->last_item_seen]) &&
1643
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1644
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1645
            //&& !$this->items[$this->last_item_seen]->is_done()
1646
        ) {
1647
            if ($this->debug > 2) {
1648
                error_log(
1649
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1650
                    $this->items[$this->last_item_seen]->get_type()
1651
                );
1652
            }
1653
            $index = -1;
1654
            foreach ($this->ordered_items as $myindex => $item_id) {
1655
                if ($item_id == $this->last_item_seen) {
1656
                    $index = $myindex;
1657
                    break;
1658
                }
1659
            }
1660
            if ($index == -1) {
1661
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1662
                if ($this->debug > 2) {
1663
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1664
                }
1665
1666
                return false;
1667
            } else {
1668
                $this->last = $this->last_item_seen;
1669
                $this->current = $this->last_item_seen;
1670
                $this->index = $index;
1671
            }
1672
        } else {
1673
            if ($this->debug > 2) {
1674
                error_log('In learnpath::first() - No last item seen', 0);
1675
            }
1676
            $index = 0;
1677
            // Loop through all ordered items and stop at the first item that is
1678
            // not a directory *and* that has not been completed yet.
1679
            while (!empty($this->ordered_items[$index]) &&
1680
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1681
                (
1682
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1683
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1684
                ) && $index < $this->max_ordered_items) {
1685
                $index++;
1686
            }
1687
1688
            $this->last = $this->current;
1689
            // current is
1690
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1691
            $this->index = $index;
1692
            if ($this->debug > 2) {
1693
                error_log('$index '.$index);
1694
                error_log('In learnpath::first() - No last item seen');
1695
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1696
            }
1697
        }
1698
        if ($this->debug > 2) {
1699
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1700
        }
1701
    }
1702
1703
    /**
1704
     * Gets the js library from the database.
1705
     *
1706
     * @return string The name of the javascript library to be used
1707
     */
1708
    public function get_js_lib()
1709
    {
1710
        $lib = '';
1711
        if (!empty($this->js_lib)) {
1712
            $lib = $this->js_lib;
1713
        }
1714
1715
        return $lib;
1716
    }
1717
1718
    /**
1719
     * Gets the learnpath database ID.
1720
     *
1721
     * @return int Learnpath ID in the lp table
1722
     */
1723
    public function get_id()
1724
    {
1725
        if (!empty($this->lp_id)) {
1726
            return (int) $this->lp_id;
1727
        }
1728
1729
        return 0;
1730
    }
1731
1732
    /**
1733
     * Gets the last element URL.
1734
     *
1735
     * @return string URL to load into the viewer
1736
     */
1737
    public function get_last()
1738
    {
1739
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1740
        if (count($this->ordered_items) > 0) {
1741
            $this->index = count($this->ordered_items) - 1;
1742
1743
            return $this->ordered_items[$this->index];
1744
        }
1745
1746
        return false;
1747
    }
1748
1749
    /**
1750
     * Get the last element in the first level.
1751
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1752
     *
1753
     * @return mixed
1754
     */
1755
    public function getLastInFirstLevel()
1756
    {
1757
        try {
1758
            $lastId = Database::getManager()
1759
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1760
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1761
                ->setMaxResults(1)
1762
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1763
                ->getSingleScalarResult();
1764
1765
            return $lastId;
1766
        } catch (Exception $exception) {
1767
            return 0;
1768
        }
1769
    }
1770
1771
    /**
1772
     * Gets the navigation bar for the learnpath display screen.
1773
     *
1774
     * @param string $barId
1775
     *
1776
     * @return string The HTML string to use as a navigation bar
1777
     */
1778
    public function get_navigation_bar($barId = '')
1779
    {
1780
        if (empty($barId)) {
1781
            $barId = 'control-top';
1782
        }
1783
        $lpId = $this->lp_id;
1784
        $mycurrentitemid = $this->get_current_item_id();
1785
1786
        $reportingText = get_lang('Reporting');
1787
        $previousText = get_lang('ScormPrevious');
1788
        $nextText = get_lang('ScormNext');
1789
        $fullScreenText = get_lang('ScormExitFullScreen');
1790
1791
        $settings = api_get_configuration_value('lp_view_settings');
1792
        $display = isset($settings['display']) ? $settings['display'] : false;
1793
        $reportingIcon = '
1794
            <a class="icon-toolbar" 
1795
                id="stats_link"
1796
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
1797
                onclick="window.parent.API.save_asset(); return true;" 
1798
                target="content_name" title="'.$reportingText.'">
1799
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1800
            </a>';
1801
1802
        if (!empty($display)) {
1803
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1804
            if ($showReporting === false) {
1805
                $reportingIcon = '';
1806
            }
1807
        }
1808
1809
        $hideArrows = false;
1810
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1811
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1812
        }
1813
1814
        $previousIcon = '';
1815
        $nextIcon = '';
1816
        if ($hideArrows === false) {
1817
            $previousIcon = '
1818
                <a class="icon-toolbar" id="scorm-previous" href="#" 
1819
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1820
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1821
                </a>';
1822
1823
            $nextIcon = '
1824
                <a class="icon-toolbar" id="scorm-next" href="#" 
1825
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1826
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1827
                </a>';
1828
        }
1829
1830
        if ($this->mode === 'fullscreen') {
1831
            $navbar = '
1832
                  <span id="'.$barId.'" class="buttons">
1833
                    '.$reportingIcon.'
1834
                    '.$previousIcon.'                    
1835
                    '.$nextIcon.'
1836
                    <a class="icon-toolbar" id="view-embedded" 
1837
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1838
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1839
                    </a>
1840
                  </span>';
1841
        } else {
1842
            $navbar = '
1843
                 <span id="'.$barId.'" class="buttons text-right">
1844
                    '.$reportingIcon.'
1845
                    '.$previousIcon.'
1846
                    '.$nextIcon.'               
1847
                </span>';
1848
        }
1849
1850
        return $navbar;
1851
    }
1852
1853
    /**
1854
     * Gets the next resource in queue (url).
1855
     *
1856
     * @return string URL to load into the viewer
1857
     */
1858
    public function get_next_index()
1859
    {
1860
        // TODO
1861
        $index = $this->index;
1862
        $index++;
1863
        while (
1864
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1865
            $index < $this->max_ordered_items
1866
        ) {
1867
            $index++;
1868
            if ($index == $this->max_ordered_items) {
1869
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1870
                    return $this->index;
1871
                }
1872
1873
                return $index;
1874
            }
1875
        }
1876
        if (empty($this->ordered_items[$index])) {
1877
            return $this->index;
1878
        }
1879
1880
        return $index;
1881
    }
1882
1883
    /**
1884
     * Gets item_id for the next element.
1885
     *
1886
     * @return int Next item (DB) ID
1887
     */
1888
    public function get_next_item_id()
1889
    {
1890
        $new_index = $this->get_next_index();
1891
        if (!empty($new_index)) {
1892
            if (isset($this->ordered_items[$new_index])) {
1893
                return $this->ordered_items[$new_index];
1894
            }
1895
        }
1896
1897
        return 0;
1898
    }
1899
1900
    /**
1901
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1902
     *
1903
     * Generally, the package provided is in the form of a zip file, so the function
1904
     * has been written to test a zip file. If not a zip, the function will return the
1905
     * default return value: ''
1906
     *
1907
     * @param string $file_path the path to the file
1908
     * @param string $file_name the original name of the file
1909
     *
1910
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1911
     */
1912
    public static function get_package_type($file_path, $file_name)
1913
    {
1914
        // Get name of the zip file without the extension.
1915
        $file_info = pathinfo($file_name);
1916
        $extension = $file_info['extension']; // Extension only.
1917
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1918
                'dll',
1919
                'exe',
1920
            ])) {
1921
            return 'oogie';
1922
        }
1923
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1924
                'dll',
1925
                'exe',
1926
            ])) {
1927
            return 'woogie';
1928
        }
1929
1930
        $zipFile = new PclZip($file_path);
1931
        // Check the zip content (real size and file extension).
1932
        $zipContentArray = $zipFile->listContent();
1933
        $package_type = '';
1934
        $manifest = '';
1935
        $aicc_match_crs = 0;
1936
        $aicc_match_au = 0;
1937
        $aicc_match_des = 0;
1938
        $aicc_match_cst = 0;
1939
1940
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1941
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1942
            foreach ($zipContentArray as $thisContent) {
1943
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1944
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1945
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
1946
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1947
                    $package_type = 'scorm';
1948
                    break; // Exit the foreach loop.
1949
                } elseif (
1950
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1951
                    in_array(
1952
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1953
                        ['crs', 'au', 'des', 'cst']
1954
                    )
1955
                ) {
1956
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1957
                    switch ($ext) {
1958
                        case 'crs':
1959
                            $aicc_match_crs = 1;
1960
                            break;
1961
                        case 'au':
1962
                            $aicc_match_au = 1;
1963
                            break;
1964
                        case 'des':
1965
                            $aicc_match_des = 1;
1966
                            break;
1967
                        case 'cst':
1968
                            $aicc_match_cst = 1;
1969
                            break;
1970
                        default:
1971
                            break;
1972
                    }
1973
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1974
                } else {
1975
                    $package_type = '';
1976
                }
1977
            }
1978
        }
1979
1980
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1981
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1982
            $package_type = 'aicc';
1983
        }
1984
1985
        // Try with chamilo course builder
1986
        if (empty($package_type)) {
1987
            $package_type = 'chamilo';
1988
        }
1989
1990
        return $package_type;
1991
    }
1992
1993
    /**
1994
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1995
     *
1996
     * @return string URL to load into the viewer
1997
     */
1998
    public function get_previous_index()
1999
    {
2000
        $index = $this->index;
2001
        if (isset($this->ordered_items[$index - 1])) {
2002
            $index--;
2003
            while (isset($this->ordered_items[$index]) &&
2004
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2005
            ) {
2006
                $index--;
2007
                if ($index < 0) {
2008
                    return $this->index;
2009
                }
2010
            }
2011
        }
2012
2013
        return $index;
2014
    }
2015
2016
    /**
2017
     * Gets item_id for the next element.
2018
     *
2019
     * @return int Previous item (DB) ID
2020
     */
2021
    public function get_previous_item_id()
2022
    {
2023
        $index = $this->get_previous_index();
2024
2025
        return $this->ordered_items[$index];
2026
    }
2027
2028
    /**
2029
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2030
     *
2031
     * @param int    $lpItemId
2032
     * @param string $autostart
2033
     *
2034
     * @return string The mediaplayer HTML
2035
     */
2036
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2037
    {
2038
        $course_id = api_get_course_int_id();
2039
        $_course = api_get_course_info();
2040
        if (empty($_course)) {
2041
            return '';
2042
        }
2043
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2044
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2045
        $lpItemId = (int) $lpItemId;
2046
2047
        /** @var learnpathItem $item */
2048
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2049
        $itemViewId = 0;
2050
        if ($item) {
2051
            $itemViewId = (int) $item->db_item_view_id;
2052
        }
2053
2054
        // Getting all the information about the item.
2055
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status 
2056
                FROM $tbl_lp_item as lpi
2057
                INNER JOIN $tbl_lp_item_view as lp_view
2058
                ON (lpi.iid = lp_view.lp_item_id)
2059
                WHERE
2060
                    lp_view.iid = $itemViewId AND
2061
                    lpi.iid = $lpItemId AND
2062
                    lp_view.c_id = $course_id";
2063
        $result = Database::query($sql);
2064
        $row = Database::fetch_assoc($result);
2065
        $output = '';
2066
2067
        if (!empty($row['audio'])) {
2068
            $list = $_SESSION['oLP']->get_toc();
2069
2070
            switch ($row['item_type']) {
2071
                case 'quiz':
2072
                    $type_quiz = false;
2073
                    foreach ($list as $toc) {
2074
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2075
                            $type_quiz = true;
2076
                        }
2077
                    }
2078
2079
                    if ($type_quiz) {
2080
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2081
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2082
                        } else {
2083
                            $autostart_audio = $autostart;
2084
                        }
2085
                    }
2086
                    break;
2087
                case TOOL_READOUT_TEXT:;
2088
                    $autostart_audio = 'false';
2089
                    break;
2090
                default:
2091
                    $autostart_audio = 'true';
2092
            }
2093
2094
            $courseInfo = api_get_course_info();
2095
            $audio = $row['audio'];
2096
2097
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2098
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2099
2100
            if (!file_exists($file)) {
2101
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2102
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2103
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2104
            }
2105
2106
            $player = Display::getMediaPlayer(
2107
                $file,
2108
                [
2109
                    'id' => 'lp_audio_media_player',
2110
                    'url' => $url,
2111
                    'autoplay' => $autostart_audio,
2112
                    'width' => '100%',
2113
                ]
2114
            );
2115
2116
            // The mp3 player.
2117
            $output = '<div id="container">';
2118
            $output .= $player;
2119
            $output .= '</div>';
2120
        }
2121
2122
        return $output;
2123
    }
2124
2125
    /**
2126
     * @param int   $studentId
2127
     * @param int   $prerequisite
2128
     * @param array $courseInfo
2129
     * @param int   $sessionId
2130
     *
2131
     * @return bool
2132
     */
2133
    public static function isBlockedByPrerequisite(
2134
        $studentId,
2135
        $prerequisite,
2136
        $courseInfo,
2137
        $sessionId
2138
    ) {
2139
        if (empty($courseInfo)) {
2140
            return false;
2141
        }
2142
2143
        $courseId = $courseInfo['real_id'];
2144
2145
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2146
        if ($allow) {
2147
            if (api_is_allowed_to_edit() ||
2148
                api_is_platform_admin(true) ||
2149
                api_is_drh() ||
2150
                api_is_coach($sessionId, $courseId, false)
2151
            ) {
2152
                return false;
2153
            }
2154
        }
2155
2156
        $isBlocked = false;
2157
        if (!empty($prerequisite)) {
2158
            $progress = self::getProgress(
2159
                $prerequisite,
2160
                $studentId,
2161
                $courseId,
2162
                $sessionId
2163
            );
2164
            if ($progress < 100) {
2165
                $isBlocked = true;
2166
            }
2167
2168
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2169
                // Block if it does not exceed minimum time
2170
                // Minimum time (in minutes) to pass the learning path
2171
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2172
2173
                if ($accumulateWorkTime > 0) {
2174
                    // Total time in course (sum of times in learning paths from course)
2175
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2176
2177
                    // Connect with the plugin_licences_course_session table
2178
                    // which indicates what percentage of the time applies
2179
                    // Minimum connection percentage
2180
                    $perc = 100;
2181
                    // Time from the course
2182
                    $tc = $accumulateWorkTimeTotal;
2183
2184
                    // Percentage of the learning paths
2185
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2186
                    // Minimum time for each learning path
2187
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2188
2189
                    // Spent time (in seconds) so far in the learning path
2190
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2191
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2192
2193
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2194
                        $isBlocked = true;
2195
                    }
2196
                }
2197
            }
2198
        }
2199
2200
        return $isBlocked;
2201
    }
2202
2203
    /**
2204
     * Checks if the learning path is visible for student after the progress
2205
     * of its prerequisite is completed, considering the time availability and
2206
     * the LP visibility.
2207
     *
2208
     * @param int   $lp_id
2209
     * @param int   $student_id
2210
     * @param array $courseInfo
2211
     * @param int   $sessionId
2212
     *
2213
     * @return bool
2214
     */
2215
    public static function is_lp_visible_for_student(
2216
        $lp_id,
2217
        $student_id,
2218
        $courseInfo = [],
2219
        $sessionId = 0
2220
    ) {
2221
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2222
        $lp_id = (int) $lp_id;
2223
        $sessionId = (int) $sessionId;
2224
2225
        if (empty($courseInfo)) {
2226
            return false;
2227
        }
2228
2229
        if (empty($sessionId)) {
2230
            $sessionId = api_get_session_id();
2231
        }
2232
2233
        $courseId = $courseInfo['real_id'];
2234
2235
        $itemInfo = api_get_item_property_info(
2236
            $courseId,
2237
            TOOL_LEARNPATH,
2238
            $lp_id,
2239
            $sessionId
2240
        );
2241
2242
        // If the item was deleted.
2243
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2244
            return false;
2245
        }
2246
2247
        // @todo remove this query and load the row info as a parameter
2248
        $table = Database::get_course_table(TABLE_LP_MAIN);
2249
        // Get current prerequisite
2250
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2251
                FROM $table
2252
                WHERE iid = $lp_id";
2253
        $rs = Database::query($sql);
2254
        $now = time();
2255
        if (Database::num_rows($rs) > 0) {
2256
            $row = Database::fetch_array($rs, 'ASSOC');
2257
2258
            if (!empty($row['category_id'])) {
2259
                $em = Database::getManager();
2260
                $category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($row['category_id']);
2261
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2262
                    return false;
2263
                }
2264
            }
2265
2266
            $prerequisite = $row['prerequisite'];
2267
            $is_visible = true;
2268
2269
            $isBlocked = self::isBlockedByPrerequisite(
2270
                $student_id,
2271
                $prerequisite,
2272
                $courseInfo,
2273
                $sessionId
2274
            );
2275
2276
            if ($isBlocked) {
2277
                $is_visible = false;
2278
            }
2279
2280
            // Also check the time availability of the LP
2281
            if ($is_visible) {
2282
                // Adding visibility restrictions
2283
                if (!empty($row['publicated_on'])) {
2284
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2285
                        $is_visible = false;
2286
                    }
2287
                }
2288
                // Blocking empty start times see BT#2800
2289
                global $_custom;
2290
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2291
                    $_custom['lps_hidden_when_no_start_date']
2292
                ) {
2293
                    if (empty($row['publicated_on'])) {
2294
                        $is_visible = false;
2295
                    }
2296
                }
2297
2298
                if (!empty($row['expired_on'])) {
2299
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2300
                        $is_visible = false;
2301
                    }
2302
                }
2303
            }
2304
2305
            if ($is_visible) {
2306
                $subscriptionSettings = self::getSubscriptionSettings();
2307
2308
                // Check if the subscription users/group to a LP is ON
2309
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2310
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2311
                ) {
2312
                    // Try group
2313
                    $is_visible = false;
2314
                    // Checking only the user visibility
2315
                    $userVisibility = api_get_item_visibility(
2316
                        $courseInfo,
2317
                        'learnpath',
2318
                        $row['id'],
2319
                        $sessionId,
2320
                        $student_id,
2321
                        'LearnpathSubscription'
2322
                    );
2323
2324
                    if ($userVisibility == 1) {
2325
                        $is_visible = true;
2326
                    } else {
2327
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2328
                        if (!empty($userGroups)) {
2329
                            foreach ($userGroups as $groupInfo) {
2330
                                $groupId = $groupInfo['iid'];
2331
                                $userVisibility = api_get_item_visibility(
2332
                                    $courseInfo,
2333
                                    'learnpath',
2334
                                    $row['id'],
2335
                                    $sessionId,
2336
                                    null,
2337
                                    'LearnpathSubscription',
2338
                                    $groupId
2339
                                );
2340
2341
                                if ($userVisibility == 1) {
2342
                                    $is_visible = true;
2343
                                    break;
2344
                                }
2345
                            }
2346
                        }
2347
                    }
2348
                }
2349
            }
2350
2351
            return $is_visible;
2352
        }
2353
2354
        return false;
2355
    }
2356
2357
    /**
2358
     * @param int $lpId
2359
     * @param int $userId
2360
     * @param int $courseId
2361
     * @param int $sessionId
2362
     *
2363
     * @return int
2364
     */
2365
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2366
    {
2367
        $lpId = (int) $lpId;
2368
        $userId = (int) $userId;
2369
        $courseId = (int) $courseId;
2370
        $sessionId = (int) $sessionId;
2371
2372
        $sessionCondition = api_get_session_condition($sessionId);
2373
        $table = Database::get_course_table(TABLE_LP_VIEW);
2374
        $sql = "SELECT progress FROM $table
2375
                WHERE
2376
                    c_id = $courseId AND
2377
                    lp_id = $lpId AND
2378
                    user_id = $userId $sessionCondition ";
2379
        $res = Database::query($sql);
2380
2381
        $progress = 0;
2382
        if (Database::num_rows($res) > 0) {
2383
            $row = Database::fetch_array($res);
2384
            $progress = (int) $row['progress'];
2385
        }
2386
2387
        return $progress;
2388
    }
2389
2390
    /**
2391
     * @param array $lpList
2392
     * @param int   $userId
2393
     * @param int   $courseId
2394
     * @param int   $sessionId
2395
     *
2396
     * @return array
2397
     */
2398
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2399
    {
2400
        $lpList = array_map('intval', $lpList);
2401
        if (empty($lpList)) {
2402
            return [];
2403
        }
2404
2405
        $lpList = implode("','", $lpList);
2406
2407
        $userId = (int) $userId;
2408
        $courseId = (int) $courseId;
2409
        $sessionId = (int) $sessionId;
2410
2411
        $sessionCondition = api_get_session_condition($sessionId);
2412
        $table = Database::get_course_table(TABLE_LP_VIEW);
2413
        $sql = "SELECT lp_id, progress FROM $table
2414
                WHERE
2415
                    c_id = $courseId AND
2416
                    lp_id IN ('".$lpList."') AND
2417
                    user_id = $userId $sessionCondition ";
2418
        $res = Database::query($sql);
2419
2420
        if (Database::num_rows($res) > 0) {
2421
            $list = [];
2422
            while ($row = Database::fetch_array($res)) {
2423
                $list[$row['lp_id']] = $row['progress'];
2424
            }
2425
2426
            return $list;
2427
        }
2428
2429
        return [];
2430
    }
2431
2432
    /**
2433
     * Displays a progress bar
2434
     * completed so far.
2435
     *
2436
     * @param int    $percentage Progress value to display
2437
     * @param string $text_add   Text to display near the progress value
2438
     *
2439
     * @return string HTML string containing the progress bar
2440
     */
2441
    public static function get_progress_bar($percentage = -1, $text_add = '')
2442
    {
2443
        $text = $percentage.$text_add;
2444
        $output = '<div class="progress">
2445
            <div id="progress_bar_value" 
2446
                class="progress-bar progress-bar-success" role="progressbar" 
2447
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2448
            '.$text.'
2449
            </div>
2450
        </div>';
2451
2452
        return $output;
2453
    }
2454
2455
    /**
2456
     * @param string $mode can be '%' or 'abs'
2457
     *                     otherwise this value will be used $this->progress_bar_mode
2458
     *
2459
     * @return string
2460
     */
2461
    public function getProgressBar($mode = null)
2462
    {
2463
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2464
2465
        return self::get_progress_bar($percentage, $text_add);
2466
    }
2467
2468
    /**
2469
     * Gets the progress bar info to display inside the progress bar.
2470
     * Also used by scorm_api.php.
2471
     *
2472
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2473
     *                     we display a number of completed elements per total elements
2474
     * @param int    $add  Additional steps to fake as completed
2475
     *
2476
     * @return array Percentage or number and symbol (% or /xx)
2477
     */
2478
    public function get_progress_bar_text($mode = '', $add = 0)
2479
    {
2480
        if (empty($mode)) {
2481
            $mode = $this->progress_bar_mode;
2482
        }
2483
        $total_items = $this->getTotalItemsCountWithoutDirs();
2484
        $completeItems = $this->get_complete_items_count();
2485
        if ($add != 0) {
2486
            $completeItems += $add;
2487
        }
2488
        $text = '';
2489
        if ($completeItems > $total_items) {
2490
            $completeItems = $total_items;
2491
        }
2492
        $percentage = 0;
2493
        if ($mode == '%') {
2494
            if ($total_items > 0) {
2495
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2496
            }
2497
            $percentage = number_format($percentage, 0);
2498
            $text = '%';
2499
        } elseif ($mode === 'abs') {
2500
            $percentage = $completeItems;
2501
            $text = '/'.$total_items;
2502
        }
2503
2504
        return [
2505
            $percentage,
2506
            $text,
2507
        ];
2508
    }
2509
2510
    /**
2511
     * Gets the progress bar mode.
2512
     *
2513
     * @return string The progress bar mode attribute
2514
     */
2515
    public function get_progress_bar_mode()
2516
    {
2517
        if (!empty($this->progress_bar_mode)) {
2518
            return $this->progress_bar_mode;
2519
        }
2520
2521
        return '%';
2522
    }
2523
2524
    /**
2525
     * Gets the learnpath theme (remote or local).
2526
     *
2527
     * @return string Learnpath theme
2528
     */
2529
    public function get_theme()
2530
    {
2531
        if (!empty($this->theme)) {
2532
            return $this->theme;
2533
        }
2534
2535
        return '';
2536
    }
2537
2538
    /**
2539
     * Gets the learnpath session id.
2540
     *
2541
     * @return int
2542
     */
2543
    public function get_lp_session_id()
2544
    {
2545
        if (!empty($this->lp_session_id)) {
2546
            return (int) $this->lp_session_id;
2547
        }
2548
2549
        return 0;
2550
    }
2551
2552
    /**
2553
     * Gets the learnpath image.
2554
     *
2555
     * @return string Web URL of the LP image
2556
     */
2557
    public function get_preview_image()
2558
    {
2559
        if (!empty($this->preview_image)) {
2560
            return $this->preview_image;
2561
        }
2562
2563
        return '';
2564
    }
2565
2566
    /**
2567
     * @param string $size
2568
     * @param string $path_type
2569
     *
2570
     * @return bool|string
2571
     */
2572
    public function get_preview_image_path($size = null, $path_type = 'web')
2573
    {
2574
        $preview_image = $this->get_preview_image();
2575
        if (isset($preview_image) && !empty($preview_image)) {
2576
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2577
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2578
2579
            if (isset($size)) {
2580
                $info = pathinfo($preview_image);
2581
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2582
2583
                if (file_exists($image_sys_path.$image_custom_size)) {
2584
                    if ($path_type == 'web') {
2585
                        return $image_path.$image_custom_size;
2586
                    } else {
2587
                        return $image_sys_path.$image_custom_size;
2588
                    }
2589
                }
2590
            } else {
2591
                if ($path_type == 'web') {
2592
                    return $image_path.$preview_image;
2593
                } else {
2594
                    return $image_sys_path.$preview_image;
2595
                }
2596
            }
2597
        }
2598
2599
        return false;
2600
    }
2601
2602
    /**
2603
     * Gets the learnpath author.
2604
     *
2605
     * @return string LP's author
2606
     */
2607
    public function get_author()
2608
    {
2609
        if (!empty($this->author)) {
2610
            return $this->author;
2611
        }
2612
2613
        return '';
2614
    }
2615
2616
    /**
2617
     * Gets hide table of contents.
2618
     *
2619
     * @return int
2620
     */
2621
    public function getHideTableOfContents()
2622
    {
2623
        return (int) $this->hide_toc_frame;
2624
    }
2625
2626
    /**
2627
     * Generate a new prerequisites string for a given item. If this item was a sco and
2628
     * its prerequisites were strings (instead of IDs), then transform those strings into
2629
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2630
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2631
     * same rule as the scormExport() method.
2632
     *
2633
     * @param int $item_id Item ID
2634
     *
2635
     * @return string Prerequisites string ready for the export as SCORM
2636
     */
2637
    public function get_scorm_prereq_string($item_id)
2638
    {
2639
        if ($this->debug > 0) {
2640
            error_log('In learnpath::get_scorm_prereq_string()');
2641
        }
2642
        if (!is_object($this->items[$item_id])) {
2643
            return false;
2644
        }
2645
        /** @var learnpathItem $oItem */
2646
        $oItem = $this->items[$item_id];
2647
        $prereq = $oItem->get_prereq_string();
2648
2649
        if (empty($prereq)) {
2650
            return '';
2651
        }
2652
        if (preg_match('/^\d+$/', $prereq) &&
2653
            isset($this->items[$prereq]) &&
2654
            is_object($this->items[$prereq])
2655
        ) {
2656
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2657
            // then simply return it (with the ITEM_ prefix).
2658
            //return 'ITEM_' . $prereq;
2659
            return $this->items[$prereq]->ref;
2660
        } else {
2661
            if (isset($this->refs_list[$prereq])) {
2662
                // It's a simple string item from which the ID can be found in the refs list,
2663
                // so we can transform it directly to an ID for export.
2664
                return $this->items[$this->refs_list[$prereq]]->ref;
2665
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2666
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2667
            } else {
2668
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2669
                // and replace them, one by one, by the internal IDs (chamilo db)
2670
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2671
                // by a space as well.
2672
                $find = [
2673
                    '&',
2674
                    '|',
2675
                    '~',
2676
                    '=',
2677
                    '<>',
2678
                    '{',
2679
                    '}',
2680
                    '*',
2681
                    '(',
2682
                    ')',
2683
                ];
2684
                $replace = [
2685
                    ' ',
2686
                    ' ',
2687
                    ' ',
2688
                    ' ',
2689
                    ' ',
2690
                    ' ',
2691
                    ' ',
2692
                    ' ',
2693
                    ' ',
2694
                    ' ',
2695
                ];
2696
                $prereq_mod = str_replace($find, $replace, $prereq);
2697
                $ids = explode(' ', $prereq_mod);
2698
                foreach ($ids as $id) {
2699
                    $id = trim($id);
2700
                    if (isset($this->refs_list[$id])) {
2701
                        $prereq = preg_replace(
2702
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2703
                            'ITEM_'.$this->refs_list[$id],
2704
                            $prereq
2705
                        );
2706
                    }
2707
                }
2708
2709
                return $prereq;
2710
            }
2711
        }
2712
    }
2713
2714
    /**
2715
     * Returns the XML DOM document's node.
2716
     *
2717
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2718
     * @param string   $id       The identifier to look for
2719
     *
2720
     * @return mixed The reference to the element found with that identifier. False if not found
2721
     */
2722
    public function get_scorm_xml_node(&$children, $id)
2723
    {
2724
        for ($i = 0; $i < $children->length; $i++) {
2725
            $item_temp = $children->item($i);
2726
            if ($item_temp->nodeName == 'item') {
2727
                if ($item_temp->getAttribute('identifier') == $id) {
2728
                    return $item_temp;
2729
                }
2730
            }
2731
            $subchildren = $item_temp->childNodes;
2732
            if ($subchildren && $subchildren->length > 0) {
2733
                $val = $this->get_scorm_xml_node($subchildren, $id);
2734
                if (is_object($val)) {
2735
                    return $val;
2736
                }
2737
            }
2738
        }
2739
2740
        return false;
2741
    }
2742
2743
    /**
2744
     * Gets the status list for all LP's items.
2745
     *
2746
     * @return array Array of [index] => [item ID => current status]
2747
     */
2748
    public function get_items_status_list()
2749
    {
2750
        $list = [];
2751
        foreach ($this->ordered_items as $item_id) {
2752
            $list[] = [
2753
                $item_id => $this->items[$item_id]->get_status(),
2754
            ];
2755
        }
2756
2757
        return $list;
2758
    }
2759
2760
    /**
2761
     * Return the number of interactions for the given learnpath Item View ID.
2762
     * This method can be used as static.
2763
     *
2764
     * @param int $lp_iv_id  Item View ID
2765
     * @param int $course_id course id
2766
     *
2767
     * @return int
2768
     */
2769
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2770
    {
2771
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2772
        $lp_iv_id = (int) $lp_iv_id;
2773
        $course_id = (int) $course_id;
2774
2775
        $sql = "SELECT count(*) FROM $table
2776
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2777
        $res = Database::query($sql);
2778
        $num = 0;
2779
        if (Database::num_rows($res)) {
2780
            $row = Database::fetch_array($res);
2781
            $num = $row[0];
2782
        }
2783
2784
        return $num;
2785
    }
2786
2787
    /**
2788
     * Return the interactions as an array for the given lp_iv_id.
2789
     * This method can be used as static.
2790
     *
2791
     * @param int $lp_iv_id Learnpath Item View ID
2792
     *
2793
     * @return array
2794
     *
2795
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2796
     */
2797
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2798
    {
2799
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2800
        $list = [];
2801
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2802
        $lp_iv_id = (int) $lp_iv_id;
2803
2804
        if (empty($lp_iv_id) || empty($course_id)) {
2805
            return [];
2806
        }
2807
2808
        $sql = "SELECT * FROM $table
2809
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2810
                ORDER BY order_id ASC";
2811
        $res = Database::query($sql);
2812
        $num = Database::num_rows($res);
2813
        if ($num > 0) {
2814
            $list[] = [
2815
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2816
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2817
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2818
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2819
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2820
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2821
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2822
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2823
                'student_response_formatted' => '',
2824
            ];
2825
            while ($row = Database::fetch_array($res)) {
2826
                $studentResponseFormatted = urldecode($row['student_response']);
2827
                $content_student_response = explode('__|', $studentResponseFormatted);
2828
                if (count($content_student_response) > 0) {
2829
                    if (count($content_student_response) >= 3) {
2830
                        // Pop the element off the end of array.
2831
                        array_pop($content_student_response);
2832
                    }
2833
                    $studentResponseFormatted = implode(',', $content_student_response);
2834
                }
2835
2836
                $list[] = [
2837
                    'order_id' => $row['order_id'] + 1,
2838
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2839
                    'type' => $row['interaction_type'],
2840
                    'time' => $row['completion_time'],
2841
                    'correct_responses' => '', // Hide correct responses from students.
2842
                    'student_response' => $row['student_response'],
2843
                    'result' => $row['result'],
2844
                    'latency' => $row['latency'],
2845
                    'student_response_formatted' => $studentResponseFormatted,
2846
                ];
2847
            }
2848
        }
2849
2850
        return $list;
2851
    }
2852
2853
    /**
2854
     * Return the number of objectives for the given learnpath Item View ID.
2855
     * This method can be used as static.
2856
     *
2857
     * @param int $lp_iv_id  Item View ID
2858
     * @param int $course_id Course ID
2859
     *
2860
     * @return int Number of objectives
2861
     */
2862
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2863
    {
2864
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2865
        $course_id = (int) $course_id;
2866
        $lp_iv_id = (int) $lp_iv_id;
2867
        $sql = "SELECT count(*) FROM $table
2868
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2869
        //@todo seems that this always returns 0
2870
        $res = Database::query($sql);
2871
        $num = 0;
2872
        if (Database::num_rows($res)) {
2873
            $row = Database::fetch_array($res);
2874
            $num = $row[0];
2875
        }
2876
2877
        return $num;
2878
    }
2879
2880
    /**
2881
     * Return the objectives as an array for the given lp_iv_id.
2882
     * This method can be used as static.
2883
     *
2884
     * @param int $lpItemViewId Learnpath Item View ID
2885
     * @param int $course_id
2886
     *
2887
     * @return array
2888
     *
2889
     * @todo    Translate labels
2890
     */
2891
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2892
    {
2893
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2894
        $lpItemViewId = (int) $lpItemViewId;
2895
2896
        if (empty($course_id) || empty($lpItemViewId)) {
2897
            return [];
2898
        }
2899
2900
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2901
        $sql = "SELECT * FROM $table
2902
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2903
                ORDER BY order_id ASC";
2904
        $res = Database::query($sql);
2905
        $num = Database::num_rows($res);
2906
        $list = [];
2907
        if ($num > 0) {
2908
            $list[] = [
2909
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2910
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
2911
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
2912
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
2913
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
2914
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
2915
            ];
2916
            while ($row = Database::fetch_array($res)) {
2917
                $list[] = [
2918
                    'order_id' => $row['order_id'] + 1,
2919
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2920
                    'score_raw' => $row['score_raw'],
2921
                    'score_max' => $row['score_max'],
2922
                    'score_min' => $row['score_min'],
2923
                    'status' => $row['status'],
2924
                ];
2925
            }
2926
        }
2927
2928
        return $list;
2929
    }
2930
2931
    /**
2932
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2933
     * used by get_html_toc() to be ready to display.
2934
     *
2935
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2936
     */
2937
    public function get_toc()
2938
    {
2939
        $toc = [];
2940
        foreach ($this->ordered_items as $item_id) {
2941
            // TODO: Change this link generation and use new function instead.
2942
            $toc[] = [
2943
                'id' => $item_id,
2944
                'title' => $this->items[$item_id]->get_title(),
2945
                'status' => $this->items[$item_id]->get_status(),
2946
                'level' => $this->items[$item_id]->get_level(),
2947
                'type' => $this->items[$item_id]->get_type(),
2948
                'description' => $this->items[$item_id]->get_description(),
2949
                'path' => $this->items[$item_id]->get_path(),
2950
                'parent' => $this->items[$item_id]->get_parent(),
2951
            ];
2952
        }
2953
2954
        return $toc;
2955
    }
2956
2957
    /**
2958
     * Generate and return the table of contents for this learnpath. The JS
2959
     * table returned is used inside of scorm_api.php.
2960
     *
2961
     * @param string $varname
2962
     *
2963
     * @return string A JS array variable construction
2964
     */
2965
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2966
    {
2967
        $toc = $varname.' = new Array();';
2968
        foreach ($this->ordered_items as $item_id) {
2969
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2970
        }
2971
2972
        return $toc;
2973
    }
2974
2975
    /**
2976
     * Gets the learning path type.
2977
     *
2978
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2979
     *
2980
     * @return mixed Type ID or name, depending on the parameter
2981
     */
2982
    public function get_type($get_name = false)
2983
    {
2984
        $res = false;
2985
        if (!empty($this->type) && (!$get_name)) {
2986
            $res = $this->type;
2987
        }
2988
2989
        return $res;
2990
    }
2991
2992
    /**
2993
     * Gets the learning path type as static method.
2994
     *
2995
     * @param int $lp_id
2996
     *
2997
     * @return mixed Type ID or name, depending on the parameter
2998
     */
2999
    public static function get_type_static($lp_id = 0)
3000
    {
3001
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3002
        $lp_id = (int) $lp_id;
3003
        $sql = "SELECT lp_type FROM $tbl_lp
3004
                WHERE iid = $lp_id";
3005
        $res = Database::query($sql);
3006
        if ($res === false) {
3007
            return null;
3008
        }
3009
        if (Database::num_rows($res) <= 0) {
3010
            return null;
3011
        }
3012
        $row = Database::fetch_array($res);
3013
3014
        return $row['lp_type'];
3015
    }
3016
3017
    /**
3018
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3019
     * This method can be used as abstract and is recursive.
3020
     *
3021
     * @param int $lp        Learnpath ID
3022
     * @param int $parent    Parent ID of the items to look for
3023
     * @param int $course_id
3024
     *
3025
     * @return array Ordered list of item IDs (empty array on error)
3026
     */
3027
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3028
    {
3029
        if (empty($course_id)) {
3030
            $course_id = api_get_course_int_id();
3031
        } else {
3032
            $course_id = (int) $course_id;
3033
        }
3034
        $list = [];
3035
3036
        if (empty($lp)) {
3037
            return $list;
3038
        }
3039
3040
        $lp = (int) $lp;
3041
        $parent = (int) $parent;
3042
3043
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3044
        $sql = "SELECT iid FROM $tbl_lp_item
3045
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3046
                ORDER BY display_order";
3047
3048
        $res = Database::query($sql);
3049
        while ($row = Database::fetch_array($res)) {
3050
            $sublist = self::get_flat_ordered_items_list(
3051
                $lp,
3052
                $row['iid'],
3053
                $course_id
3054
            );
3055
            $list[] = $row['iid'];
3056
            foreach ($sublist as $item) {
3057
                $list[] = $item;
3058
            }
3059
        }
3060
3061
        return $list;
3062
    }
3063
3064
    /**
3065
     * @return array
3066
     */
3067
    public static function getChapterTypes()
3068
    {
3069
        return [
3070
            'dir',
3071
        ];
3072
    }
3073
3074
    /**
3075
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3076
     *
3077
     * @param $tree
3078
     *
3079
     * @return array HTML TOC ready to display
3080
     */
3081
    public function getParentToc($tree)
3082
    {
3083
        if (empty($tree)) {
3084
            $tree = $this->get_toc();
3085
        }
3086
        $dirTypes = self::getChapterTypes();
3087
        $myCurrentId = $this->get_current_item_id();
3088
        $listParent = [];
3089
        $listChildren = [];
3090
        $listNotParent = [];
3091
        $list = [];
3092
        foreach ($tree as $subtree) {
3093
            if (in_array($subtree['type'], $dirTypes)) {
3094
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3095
                $subtree['children'] = $listChildren;
3096
                if (!empty($subtree['children'])) {
3097
                    foreach ($subtree['children'] as $subItem) {
3098
                        if ($subItem['id'] == $this->current) {
3099
                            $subtree['parent_current'] = 'in';
3100
                            $subtree['current'] = 'on';
3101
                        }
3102
                    }
3103
                }
3104
                $listParent[] = $subtree;
3105
            }
3106
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3107
                $classStatus = [
3108
                    'not attempted' => 'scorm_not_attempted',
3109
                    'incomplete' => 'scorm_not_attempted',
3110
                    'failed' => 'scorm_failed',
3111
                    'completed' => 'scorm_completed',
3112
                    'passed' => 'scorm_completed',
3113
                    'succeeded' => 'scorm_completed',
3114
                    'browsed' => 'scorm_completed',
3115
                ];
3116
3117
                if (isset($classStatus[$subtree['status']])) {
3118
                    $cssStatus = $classStatus[$subtree['status']];
3119
                }
3120
3121
                $title = Security::remove_XSS($subtree['title']);
3122
                unset($subtree['title']);
3123
3124
                if (empty($title)) {
3125
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3126
                }
3127
                $classStyle = null;
3128
                if ($subtree['id'] == $this->current) {
3129
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3130
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3131
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3132
                }
3133
                $subtree['title'] = $title;
3134
                $subtree['class'] = $classStyle.' '.$cssStatus;
3135
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3136
                $subtree['current_id'] = $myCurrentId;
3137
                $listNotParent[] = $subtree;
3138
            }
3139
        }
3140
3141
        $list['are_parents'] = $listParent;
3142
        $list['not_parents'] = $listNotParent;
3143
3144
        return $list;
3145
    }
3146
3147
    /**
3148
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3149
     *
3150
     * @param array $tree
3151
     * @param int   $id
3152
     * @param bool  $parent
3153
     *
3154
     * @return array HTML TOC ready to display
3155
     */
3156
    public function getChildrenToc($tree, $id, $parent = true)
3157
    {
3158
        if (empty($tree)) {
3159
            $tree = $this->get_toc();
3160
        }
3161
3162
        $dirTypes = self::getChapterTypes();
3163
        $currentItemId = $this->get_current_item_id();
3164
        $list = [];
3165
        $classStatus = [
3166
            'not attempted' => 'scorm_not_attempted',
3167
            'incomplete' => 'scorm_not_attempted',
3168
            'failed' => 'scorm_failed',
3169
            'completed' => 'scorm_completed',
3170
            'passed' => 'scorm_completed',
3171
            'succeeded' => 'scorm_completed',
3172
            'browsed' => 'scorm_completed',
3173
        ];
3174
3175
        foreach ($tree as $subtree) {
3176
            $subtree['tree'] = null;
3177
3178
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3179
                if ($subtree['id'] == $this->current) {
3180
                    $subtree['current'] = 'active';
3181
                } else {
3182
                    $subtree['current'] = null;
3183
                }
3184
                if (isset($classStatus[$subtree['status']])) {
3185
                    $cssStatus = $classStatus[$subtree['status']];
3186
                }
3187
3188
                $title = Security::remove_XSS($subtree['title']);
3189
                unset($subtree['title']);
3190
                if (empty($title)) {
3191
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3192
                }
3193
3194
                $classStyle = null;
3195
                if ($subtree['id'] == $this->current) {
3196
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3197
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3198
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3199
                }
3200
3201
                if (in_array($subtree['type'], $dirTypes)) {
3202
                    $subtree['title'] = stripslashes($title);
3203
                } else {
3204
                    $subtree['title'] = $title;
3205
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3206
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3207
                    $subtree['current_id'] = $currentItemId;
3208
                }
3209
                $list[] = $subtree;
3210
            }
3211
        }
3212
3213
        return $list;
3214
    }
3215
3216
    /**
3217
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3218
     *
3219
     * @param array $toc_list
3220
     *
3221
     * @return array HTML TOC ready to display
3222
     */
3223
    public function getListArrayToc($toc_list = [])
3224
    {
3225
        if (empty($toc_list)) {
3226
            $toc_list = $this->get_toc();
3227
        }
3228
        // Temporary variables.
3229
        $currentItemId = $this->get_current_item_id();
3230
        $list = [];
3231
        $arrayList = [];
3232
        $classStatus = [
3233
            'not attempted' => 'scorm_not_attempted',
3234
            'incomplete' => 'scorm_not_attempted',
3235
            'failed' => 'scorm_failed',
3236
            'completed' => 'scorm_completed',
3237
            'passed' => 'scorm_completed',
3238
            'succeeded' => 'scorm_completed',
3239
            'browsed' => 'scorm_completed',
3240
        ];
3241
3242
        foreach ($toc_list as $item) {
3243
            $list['id'] = $item['id'];
3244
            $list['status'] = $item['status'];
3245
            $cssStatus = null;
3246
3247
            if (isset($classStatus[$item['status']])) {
3248
                $cssStatus = $classStatus[$item['status']];
3249
            }
3250
3251
            $classStyle = ' ';
3252
            $dirTypes = self::getChapterTypes();
3253
3254
            if (in_array($item['type'], $dirTypes)) {
3255
                $classStyle = 'scorm_item_section ';
3256
            }
3257
            if ($item['id'] == $this->current) {
3258
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3259
            } elseif (!in_array($item['type'], $dirTypes)) {
3260
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3261
            }
3262
            $title = $item['title'];
3263
            if (empty($title)) {
3264
                $title = self::rl_get_resource_name(
3265
                    api_get_course_id(),
3266
                    $this->get_id(),
3267
                    $item['id']
3268
                );
3269
            }
3270
            $title = Security::remove_XSS($item['title']);
3271
3272
            if (empty($item['description'])) {
3273
                $list['description'] = $title;
3274
            } else {
3275
                $list['description'] = $item['description'];
3276
            }
3277
3278
            $list['class'] = $classStyle.' '.$cssStatus;
3279
            $list['level'] = $item['level'];
3280
            $list['type'] = $item['type'];
3281
3282
            if (in_array($item['type'], $dirTypes)) {
3283
                $list['css_level'] = 'level_'.$item['level'];
3284
            } else {
3285
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3286
            }
3287
3288
            if (in_array($item['type'], $dirTypes)) {
3289
                $list['title'] = stripslashes($title);
3290
            } else {
3291
                $list['title'] = stripslashes($title);
3292
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3293
                $list['current_id'] = $currentItemId;
3294
            }
3295
            $arrayList[] = $list;
3296
        }
3297
3298
        return $arrayList;
3299
    }
3300
3301
    /**
3302
     * Returns an HTML-formatted string ready to display with teacher buttons
3303
     * in LP view menu.
3304
     *
3305
     * @return string HTML TOC ready to display
3306
     */
3307
    public function get_teacher_toc_buttons()
3308
    {
3309
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3310
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3311
        $html = '';
3312
        if ($isAllow && $hideIcons == false) {
3313
            if ($this->get_lp_session_id() == api_get_session_id()) {
3314
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3315
                $html .= '<div class="btn-group">';
3316
                $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'>".
3317
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3318
                $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'>".
3319
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3320
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3321
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3322
                $html .= '</div>';
3323
                $html .= '</div>';
3324
            }
3325
        }
3326
3327
        return $html;
3328
    }
3329
3330
    /**
3331
     * Gets the learnpath maker name - generally the editor's name.
3332
     *
3333
     * @return string Learnpath maker name
3334
     */
3335
    public function get_maker()
3336
    {
3337
        if (!empty($this->maker)) {
3338
            return $this->maker;
3339
        }
3340
3341
        return '';
3342
    }
3343
3344
    /**
3345
     * Gets the learnpath name/title.
3346
     *
3347
     * @return string Learnpath name/title
3348
     */
3349
    public function get_name()
3350
    {
3351
        if (!empty($this->name)) {
3352
            return $this->name;
3353
        }
3354
3355
        return 'N/A';
3356
    }
3357
3358
    /**
3359
     * @return string
3360
     */
3361
    public function getNameNoTags()
3362
    {
3363
        return strip_tags($this->get_name());
3364
    }
3365
3366
    /**
3367
     * Gets a link to the resource from the present location, depending on item ID.
3368
     *
3369
     * @param string $type         Type of link expected
3370
     * @param int    $item_id      Learnpath item ID
3371
     * @param bool   $provided_toc
3372
     *
3373
     * @return string $provided_toc Link to the lp_item resource
3374
     */
3375
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3376
    {
3377
        $course_id = $this->get_course_int_id();
3378
        $item_id = (int) $item_id;
3379
3380
        if (empty($item_id)) {
3381
            $item_id = $this->get_current_item_id();
3382
3383
            if (empty($item_id)) {
3384
                //still empty, this means there was no item_id given and we are not in an object context or
3385
                //the object property is empty, return empty link
3386
                $this->first();
3387
3388
                return '';
3389
            }
3390
        }
3391
3392
        $file = '';
3393
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3394
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3395
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3396
3397
        $sql = "SELECT
3398
                    l.lp_type as ltype,
3399
                    l.path as lpath,
3400
                    li.item_type as litype,
3401
                    li.path as lipath,
3402
                    li.parameters as liparams
3403
        		FROM $lp_table l
3404
                INNER JOIN $lp_item_table li
3405
                ON (li.lp_id = l.iid)
3406
        		WHERE
3407
        		    li.iid = $item_id
3408
        		";
3409
        $res = Database::query($sql);
3410
        if (Database::num_rows($res) > 0) {
3411
            $row = Database::fetch_array($res);
3412
            $lp_type = $row['ltype'];
3413
            $lp_path = $row['lpath'];
3414
            $lp_item_type = $row['litype'];
3415
            $lp_item_path = $row['lipath'];
3416
            $lp_item_params = $row['liparams'];
3417
3418
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3419
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3420
            }
3421
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3422
            if ($type === 'http') {
3423
                //web path
3424
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3425
            } else {
3426
                $course_path = $sys_course_path; //system path
3427
            }
3428
3429
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3430
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3431
            if (in_array(
3432
                $lp_item_type,
3433
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3434
            )
3435
            ) {
3436
                $lp_type = 1;
3437
            }
3438
3439
            // Now go through the specific cases to get the end of the path
3440
            // @todo Use constants instead of int values.
3441
            switch ($lp_type) {
3442
                case 1:
3443
                    $file = self::rl_get_resource_link_for_learnpath(
3444
                        $course_id,
3445
                        $this->get_id(),
3446
                        $item_id,
3447
                        $this->get_view_id()
3448
                    );
3449
                    switch ($lp_item_type) {
3450
                        case 'document':
3451
                            // Shows a button to download the file instead of just downloading the file directly.
3452
                            $documentPathInfo = pathinfo($file);
3453
                            if (isset($documentPathInfo['extension'])) {
3454
                                $parsed = parse_url($documentPathInfo['extension']);
3455
                                if (isset($parsed['path'])) {
3456
                                    $extension = $parsed['path'];
3457
                                    $extensionsToDownload = [
3458
                                        'zip',
3459
                                        'ppt',
3460
                                        'pptx',
3461
                                        'ods',
3462
                                        'xlsx',
3463
                                        'xls',
3464
                                        'csv',
3465
                                        'doc',
3466
                                        'docx',
3467
                                        'dot',
3468
                                    ];
3469
3470
                                    if (in_array($extension, $extensionsToDownload)) {
3471
                                        $file = api_get_path(WEB_CODE_PATH).
3472
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3473
                                    }
3474
                                }
3475
                            }
3476
                            break;
3477
                        case 'dir':
3478
                            $file = 'lp_content.php?type=dir';
3479
                            break;
3480
                        case 'link':
3481
                            if (Link::is_youtube_link($file)) {
3482
                                $src = Link::get_youtube_video_id($file);
3483
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3484
                            } elseif (Link::isVimeoLink($file)) {
3485
                                $src = Link::getVimeoLinkId($file);
3486
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3487
                            } else {
3488
                                // If the current site is HTTPS and the link is
3489
                                // HTTP, browsers will refuse opening the link
3490
                                $urlId = api_get_current_access_url_id();
3491
                                $url = api_get_access_url($urlId, false);
3492
                                $protocol = substr($url['url'], 0, 5);
3493
                                if ($protocol === 'https') {
3494
                                    $linkProtocol = substr($file, 0, 5);
3495
                                    if ($linkProtocol === 'http:') {
3496
                                        //this is the special intervention case
3497
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3498
                                    }
3499
                                }
3500
                            }
3501
                            break;
3502
                        case 'quiz':
3503
                            // Check how much attempts of a exercise exits in lp
3504
                            $lp_item_id = $this->get_current_item_id();
3505
                            $lp_view_id = $this->get_view_id();
3506
3507
                            $prevent_reinit = null;
3508
                            if (isset($this->items[$this->current])) {
3509
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3510
                            }
3511
3512
                            if (empty($provided_toc)) {
3513
                                if ($this->debug > 0) {
3514
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3515
                                }
3516
                                $list = $this->get_toc();
3517
                            } else {
3518
                                if ($this->debug > 0) {
3519
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3520
                                }
3521
                                $list = $provided_toc;
3522
                            }
3523
3524
                            $type_quiz = false;
3525
                            foreach ($list as $toc) {
3526
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3527
                                    $type_quiz = true;
3528
                                }
3529
                            }
3530
3531
                            if ($type_quiz) {
3532
                                $lp_item_id = (int) $lp_item_id;
3533
                                $lp_view_id = (int) $lp_view_id;
3534
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3535
                                        WHERE
3536
                                            c_id = $course_id AND
3537
                                            lp_item_id='".$lp_item_id."' AND
3538
                                            lp_view_id ='".$lp_view_id."' AND
3539
                                            status='completed'";
3540
                                $result = Database::query($sql);
3541
                                $row_count = Database:: fetch_row($result);
3542
                                $count_item_view = (int) $row_count[0];
3543
                                $not_multiple_attempt = 0;
3544
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3545
                                    $not_multiple_attempt = 1;
3546
                                }
3547
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3548
                            }
3549
                            break;
3550
                    }
3551
3552
                    $tmp_array = explode('/', $file);
3553
                    $document_name = $tmp_array[count($tmp_array) - 1];
3554
                    if (strpos($document_name, '_DELETED_')) {
3555
                        $file = 'blank.php?error=document_deleted';
3556
                    }
3557
                    break;
3558
                case 2:
3559
                    if ($this->debug > 2) {
3560
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3561
                    }
3562
3563
                    if ($lp_item_type != 'dir') {
3564
                        // Quite complex here:
3565
                        // We want to make sure 'http://' (and similar) links can
3566
                        // be loaded as is (withouth the Chamilo path in front) but
3567
                        // some contents use this form: resource.htm?resource=http://blablabla
3568
                        // which means we have to find a protocol at the path's start, otherwise
3569
                        // it should not be considered as an external URL.
3570
                        // if ($this->prerequisites_match($item_id)) {
3571
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3572
                            if ($this->debug > 2) {
3573
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3574
                            }
3575
                            // Distant url, return as is.
3576
                            $file = $lp_item_path;
3577
                        } else {
3578
                            if ($this->debug > 2) {
3579
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3580
                            }
3581
                            // Prevent getting untranslatable urls.
3582
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3583
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3584
                            // Prepare the path.
3585
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3586
                            // TODO: Fix this for urls with protocol header.
3587
                            $file = str_replace('//', '/', $file);
3588
                            $file = str_replace(':/', '://', $file);
3589
                            if (substr($lp_path, -1) == '/') {
3590
                                $lp_path = substr($lp_path, 0, -1);
3591
                            }
3592
3593
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3594
                                // if file not found.
3595
                                $decoded = html_entity_decode($lp_item_path);
3596
                                list($decoded) = explode('?', $decoded);
3597
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3598
                                    $file = self::rl_get_resource_link_for_learnpath(
3599
                                        $course_id,
3600
                                        $this->get_id(),
3601
                                        $item_id,
3602
                                        $this->get_view_id()
3603
                                    );
3604
                                    if (empty($file)) {
3605
                                        $file = 'blank.php?error=document_not_found';
3606
                                    } else {
3607
                                        $tmp_array = explode('/', $file);
3608
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3609
                                        if (strpos($document_name, '_DELETED_')) {
3610
                                            $file = 'blank.php?error=document_deleted';
3611
                                        } else {
3612
                                            $file = 'blank.php?error=document_not_found';
3613
                                        }
3614
                                    }
3615
                                } else {
3616
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3617
                                }
3618
                            }
3619
                        }
3620
3621
                        // We want to use parameters if they were defined in the imsmanifest
3622
                        if (strpos($file, 'blank.php') === false) {
3623
                            $lp_item_params = ltrim($lp_item_params, '?');
3624
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3625
                        }
3626
                    } else {
3627
                        $file = 'lp_content.php?type=dir';
3628
                    }
3629
                    break;
3630
                case 3:
3631
                    if ($this->debug > 2) {
3632
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3633
                    }
3634
                    // Formatting AICC HACP append URL.
3635
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3636
                    if (!empty($lp_item_params)) {
3637
                        $aicc_append .= $lp_item_params.'&';
3638
                    }
3639
                    if ($lp_item_type != 'dir') {
3640
                        // Quite complex here:
3641
                        // We want to make sure 'http://' (and similar) links can
3642
                        // be loaded as is (withouth the Chamilo path in front) but
3643
                        // some contents use this form: resource.htm?resource=http://blablabla
3644
                        // which means we have to find a protocol at the path's start, otherwise
3645
                        // it should not be considered as an external URL.
3646
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3647
                            if ($this->debug > 2) {
3648
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3649
                            }
3650
                            // Distant url, return as is.
3651
                            $file = $lp_item_path;
3652
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3653
                            /*
3654
                            if (stristr($file,'<servername>') !== false) {
3655
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3656
                            }
3657
                            */
3658
                            if (stripos($file, '<servername>') !== false) {
3659
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3660
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3661
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3662
                            }
3663
3664
                            $file .= $aicc_append;
3665
                        } else {
3666
                            if ($this->debug > 2) {
3667
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3668
                            }
3669
                            // Prevent getting untranslatable urls.
3670
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3671
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3672
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3673
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3674
                            // TODO: Fix this for urls with protocol header.
3675
                            $file = str_replace('//', '/', $file);
3676
                            $file = str_replace(':/', '://', $file);
3677
                            $file .= $aicc_append;
3678
                        }
3679
                    } else {
3680
                        $file = 'lp_content.php?type=dir';
3681
                    }
3682
                    break;
3683
                case 4:
3684
                    break;
3685
                default:
3686
                    break;
3687
            }
3688
            // Replace &amp; by & because &amp; will break URL with params
3689
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3690
        }
3691
        if ($this->debug > 2) {
3692
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3693
        }
3694
3695
        return $file;
3696
    }
3697
3698
    /**
3699
     * Gets the latest usable view or generate a new one.
3700
     *
3701
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3702
     *
3703
     * @return int DB lp_view id
3704
     */
3705
    public function get_view($attempt_num = 0)
3706
    {
3707
        $search = '';
3708
        // Use $attempt_num to enable multi-views management (disabled so far).
3709
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3710
            $search = 'AND view_count = '.$attempt_num;
3711
        }
3712
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3713
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3714
3715
        $course_id = api_get_course_int_id();
3716
        $sessionId = api_get_session_id();
3717
3718
        $sql = "SELECT iid, view_count FROM $lp_view_table
3719
        		WHERE
3720
        		    c_id = $course_id AND
3721
        		    lp_id = ".$this->get_id()." AND
3722
        		    user_id = ".$this->get_user_id()." AND
3723
        		    session_id = $sessionId
3724
        		    $search
3725
                ORDER BY view_count DESC";
3726
        $res = Database::query($sql);
3727
        if (Database::num_rows($res) > 0) {
3728
            $row = Database::fetch_array($res);
3729
            $this->lp_view_id = $row['iid'];
3730
        } elseif (!api_is_invitee()) {
3731
            // There is no database record, create one.
3732
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3733
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3734
            Database::query($sql);
3735
            $id = Database::insert_id();
3736
            $this->lp_view_id = $id;
3737
3738
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3739
            Database::query($sql);
3740
        }
3741
3742
        return $this->lp_view_id;
3743
    }
3744
3745
    /**
3746
     * Gets the current view id.
3747
     *
3748
     * @return int View ID (from lp_view)
3749
     */
3750
    public function get_view_id()
3751
    {
3752
        if (!empty($this->lp_view_id)) {
3753
            return (int) $this->lp_view_id;
3754
        }
3755
3756
        return 0;
3757
    }
3758
3759
    /**
3760
     * Gets the update queue.
3761
     *
3762
     * @return array Array containing IDs of items to be updated by JavaScript
3763
     */
3764
    public function get_update_queue()
3765
    {
3766
        return $this->update_queue;
3767
    }
3768
3769
    /**
3770
     * Gets the user ID.
3771
     *
3772
     * @return int User ID
3773
     */
3774
    public function get_user_id()
3775
    {
3776
        if (!empty($this->user_id)) {
3777
            return (int) $this->user_id;
3778
        }
3779
3780
        return false;
3781
    }
3782
3783
    /**
3784
     * Checks if any of the items has an audio element attached.
3785
     *
3786
     * @return bool True or false
3787
     */
3788
    public function has_audio()
3789
    {
3790
        $has = false;
3791
        foreach ($this->items as $i => $item) {
3792
            if (!empty($this->items[$i]->audio)) {
3793
                $has = true;
3794
                break;
3795
            }
3796
        }
3797
3798
        return $has;
3799
    }
3800
3801
    /**
3802
     * Moves an item up and down at its level.
3803
     *
3804
     * @param int    $id        Item to move up and down
3805
     * @param string $direction Direction 'up' or 'down'
3806
     *
3807
     * @return bool|int
3808
     */
3809
    public function move_item($id, $direction)
3810
    {
3811
        $course_id = api_get_course_int_id();
3812
        if (empty($id) || empty($direction)) {
3813
            return false;
3814
        }
3815
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3816
        $sql_sel = "SELECT *
3817
                    FROM $tbl_lp_item
3818
                    WHERE
3819
                        iid = $id
3820
                    ";
3821
        $res_sel = Database::query($sql_sel);
3822
        // Check if elem exists.
3823
        if (Database::num_rows($res_sel) < 1) {
3824
            return false;
3825
        }
3826
        // Gather data.
3827
        $row = Database::fetch_array($res_sel);
3828
        $previous = $row['previous_item_id'];
3829
        $next = $row['next_item_id'];
3830
        $display = $row['display_order'];
3831
        $parent = $row['parent_item_id'];
3832
        $lp = $row['lp_id'];
3833
        // Update the item (switch with previous/next one).
3834
        switch ($direction) {
3835
            case 'up':
3836
                if ($display > 1) {
3837
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3838
                                 WHERE iid = $previous";
3839
                    $res_sel2 = Database::query($sql_sel2);
3840
                    if (Database::num_rows($res_sel2) < 1) {
3841
                        $previous_previous = 0;
3842
                    }
3843
                    // Gather data.
3844
                    $row2 = Database::fetch_array($res_sel2);
3845
                    $previous_previous = $row2['previous_item_id'];
3846
                    // Update previous_previous item (switch "next" with current).
3847
                    if ($previous_previous != 0) {
3848
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3849
                                        next_item_id = $id
3850
                                    WHERE iid = $previous_previous";
3851
                        Database::query($sql_upd2);
3852
                    }
3853
                    // Update previous item (switch with current).
3854
                    if ($previous != 0) {
3855
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3856
                                    next_item_id = $next,
3857
                                    previous_item_id = $id,
3858
                                    display_order = display_order +1
3859
                                    WHERE iid = $previous";
3860
                        Database::query($sql_upd2);
3861
                    }
3862
3863
                    // Update current item (switch with previous).
3864
                    if ($id != 0) {
3865
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3866
                                        next_item_id = $previous,
3867
                                        previous_item_id = $previous_previous,
3868
                                        display_order = display_order-1
3869
                                    WHERE c_id = ".$course_id." AND id = $id";
3870
                        Database::query($sql_upd2);
3871
                    }
3872
                    // Update next item (new previous item).
3873
                    if (!empty($next)) {
3874
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3875
                                     WHERE iid = $next";
3876
                        Database::query($sql_upd2);
3877
                    }
3878
                    $display = $display - 1;
3879
                }
3880
                break;
3881
            case 'down':
3882
                if ($next != 0) {
3883
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3884
                                 WHERE iid = $next";
3885
                    $res_sel2 = Database::query($sql_sel2);
3886
                    if (Database::num_rows($res_sel2) < 1) {
3887
                        $next_next = 0;
3888
                    }
3889
                    // Gather data.
3890
                    $row2 = Database::fetch_array($res_sel2);
3891
                    $next_next = $row2['next_item_id'];
3892
                    // Update previous item (switch with current).
3893
                    if ($previous != 0) {
3894
                        $sql_upd2 = "UPDATE $tbl_lp_item
3895
                                     SET next_item_id = $next
3896
                                     WHERE iid = $previous";
3897
                        Database::query($sql_upd2);
3898
                    }
3899
                    // Update current item (switch with previous).
3900
                    if ($id != 0) {
3901
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3902
                                     previous_item_id = $next,
3903
                                     next_item_id = $next_next,
3904
                                     display_order = display_order + 1
3905
                                     WHERE iid = $id";
3906
                        Database::query($sql_upd2);
3907
                    }
3908
3909
                    // Update next item (new previous item).
3910
                    if ($next != 0) {
3911
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3912
                                     previous_item_id = $previous,
3913
                                     next_item_id = $id,
3914
                                     display_order = display_order-1
3915
                                     WHERE iid = $next";
3916
                        Database::query($sql_upd2);
3917
                    }
3918
3919
                    // Update next_next item (switch "previous" with current).
3920
                    if ($next_next != 0) {
3921
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3922
                                     previous_item_id = $id
3923
                                     WHERE iid = $next_next";
3924
                        Database::query($sql_upd2);
3925
                    }
3926
                    $display = $display + 1;
3927
                }
3928
                break;
3929
            default:
3930
                return false;
3931
        }
3932
3933
        return $display;
3934
    }
3935
3936
    /**
3937
     * Move a LP up (display_order).
3938
     *
3939
     * @param int $lp_id      Learnpath ID
3940
     * @param int $categoryId Category ID
3941
     *
3942
     * @return bool
3943
     */
3944
    public static function move_up($lp_id, $categoryId = 0)
3945
    {
3946
        $courseId = api_get_course_int_id();
3947
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3948
3949
        $categoryCondition = '';
3950
        if (!empty($categoryId)) {
3951
            $categoryId = (int) $categoryId;
3952
            $categoryCondition = " AND category_id = $categoryId";
3953
        }
3954
        $sql = "SELECT * FROM $lp_table
3955
                WHERE c_id = $courseId
3956
                $categoryCondition
3957
                ORDER BY display_order";
3958
        $res = Database::query($sql);
3959
        if ($res === false) {
3960
            return false;
3961
        }
3962
3963
        $lps = [];
3964
        $lp_order = [];
3965
        $num = Database::num_rows($res);
3966
        // First check the order is correct, globally (might be wrong because
3967
        // of versions < 1.8.4)
3968
        if ($num > 0) {
3969
            $i = 1;
3970
            while ($row = Database::fetch_array($res)) {
3971
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3972
                    $sql = "UPDATE $lp_table SET display_order = $i
3973
                            WHERE iid = ".$row['iid'];
3974
                    Database::query($sql);
3975
                }
3976
                $row['display_order'] = $i;
3977
                $lps[$row['iid']] = $row;
3978
                $lp_order[$i] = $row['iid'];
3979
                $i++;
3980
            }
3981
        }
3982
        if ($num > 1) { // If there's only one element, no need to sort.
3983
            $order = $lps[$lp_id]['display_order'];
3984
            if ($order > 1) { // If it's the first element, no need to move up.
3985
                $sql = "UPDATE $lp_table SET display_order = $order
3986
                        WHERE iid = ".$lp_order[$order - 1];
3987
                Database::query($sql);
3988
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3989
                        WHERE iid = $lp_id";
3990
                Database::query($sql);
3991
            }
3992
        }
3993
3994
        return true;
3995
    }
3996
3997
    /**
3998
     * Move a learnpath down (display_order).
3999
     *
4000
     * @param int $lp_id      Learnpath ID
4001
     * @param int $categoryId Category ID
4002
     *
4003
     * @return bool
4004
     */
4005
    public static function move_down($lp_id, $categoryId = 0)
4006
    {
4007
        $courseId = api_get_course_int_id();
4008
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4009
4010
        $categoryCondition = '';
4011
        if (!empty($categoryId)) {
4012
            $categoryId = (int) $categoryId;
4013
            $categoryCondition = " AND category_id = $categoryId";
4014
        }
4015
4016
        $sql = "SELECT * FROM $lp_table
4017
                WHERE c_id = $courseId
4018
                $categoryCondition
4019
                ORDER BY display_order";
4020
        $res = Database::query($sql);
4021
        if ($res === false) {
4022
            return false;
4023
        }
4024
        $lps = [];
4025
        $lp_order = [];
4026
        $num = Database::num_rows($res);
4027
        $max = 0;
4028
        // First check the order is correct, globally (might be wrong because
4029
        // of versions < 1.8.4).
4030
        if ($num > 0) {
4031
            $i = 1;
4032
            while ($row = Database::fetch_array($res)) {
4033
                $max = $i;
4034
                if ($row['display_order'] != $i) {
4035
                    // If we find a gap in the order, we need to fix it.
4036
                    $sql = "UPDATE $lp_table SET display_order = $i
4037
                              WHERE iid = ".$row['iid'];
4038
                    Database::query($sql);
4039
                }
4040
                $row['display_order'] = $i;
4041
                $lps[$row['iid']] = $row;
4042
                $lp_order[$i] = $row['iid'];
4043
                $i++;
4044
            }
4045
        }
4046
        if ($num > 1) { // If there's only one element, no need to sort.
4047
            $order = $lps[$lp_id]['display_order'];
4048
            if ($order < $max) { // If it's the first element, no need to move up.
4049
                $sql = "UPDATE $lp_table SET display_order = $order
4050
                        WHERE iid = ".$lp_order[$order + 1];
4051
                Database::query($sql);
4052
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4053
                        WHERE iid = $lp_id";
4054
                Database::query($sql);
4055
            }
4056
        }
4057
4058
        return true;
4059
    }
4060
4061
    /**
4062
     * Updates learnpath attributes to point to the next element
4063
     * The last part is similar to set_current_item but processing the other way around.
4064
     */
4065
    public function next()
4066
    {
4067
        if ($this->debug > 0) {
4068
            error_log('In learnpath::next()', 0);
4069
        }
4070
        $this->last = $this->get_current_item_id();
4071
        $this->items[$this->last]->save(
4072
            false,
4073
            $this->prerequisites_match($this->last)
4074
        );
4075
        $this->autocomplete_parents($this->last);
4076
        $new_index = $this->get_next_index();
4077
        if ($this->debug > 2) {
4078
            error_log('New index: '.$new_index, 0);
4079
        }
4080
        $this->index = $new_index;
4081
        if ($this->debug > 2) {
4082
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4083
        }
4084
        $this->current = $this->ordered_items[$new_index];
4085
        if ($this->debug > 2) {
4086
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4087
        }
4088
    }
4089
4090
    /**
4091
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4092
     * class, this might be redefined to allow several behaviours depending on the document type.
4093
     *
4094
     * @param int $id Resource ID
4095
     */
4096
    public function open($id)
4097
    {
4098
        // TODO:
4099
        // set the current resource attribute to this resource
4100
        // switch on element type (redefine in child class?)
4101
        // set status for this item to "opened"
4102
        // start timer
4103
        // initialise score
4104
        $this->index = 0; //or = the last item seen (see $this->last)
4105
    }
4106
4107
    /**
4108
     * Check that all prerequisites are fulfilled. Returns true and an
4109
     * empty string on success, returns false
4110
     * and the prerequisite string on error.
4111
     * This function is based on the rules for aicc_script language as
4112
     * described in the SCORM 1.2 CAM documentation page 108.
4113
     *
4114
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4115
     *
4116
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4117
     *              string otherwise
4118
     */
4119
    public function prerequisites_match($itemId = null)
4120
    {
4121
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4122
        if ($allow) {
4123
            if (api_is_allowed_to_edit() ||
4124
                api_is_platform_admin(true) ||
4125
                api_is_drh() ||
4126
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4127
            ) {
4128
                return true;
4129
            }
4130
        }
4131
4132
        $debug = $this->debug;
4133
        if ($debug > 0) {
4134
            error_log('In learnpath::prerequisites_match()');
4135
        }
4136
4137
        if (empty($itemId)) {
4138
            $itemId = $this->current;
4139
        }
4140
4141
        $currentItem = $this->getItem($itemId);
4142
4143
        if ($currentItem) {
4144
            if ($this->type == 2) {
4145
                // Getting prereq from scorm
4146
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4147
            } else {
4148
                $prereq_string = $currentItem->get_prereq_string();
4149
            }
4150
4151
            if (empty($prereq_string)) {
4152
                if ($debug > 0) {
4153
                    error_log('Found prereq_string is empty return true');
4154
                }
4155
4156
                return true;
4157
            }
4158
4159
            // Clean spaces.
4160
            $prereq_string = str_replace(' ', '', $prereq_string);
4161
            if ($debug > 0) {
4162
                error_log('Found prereq_string: '.$prereq_string, 0);
4163
            }
4164
4165
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4166
            $result = $currentItem->parse_prereq(
4167
                $prereq_string,
4168
                $this->items,
4169
                $this->refs_list,
4170
                $this->get_user_id()
4171
            );
4172
4173
            if ($result === false) {
4174
                $this->set_error_msg($currentItem->prereq_alert);
4175
            }
4176
        } else {
4177
            $result = true;
4178
            if ($debug > 1) {
4179
                error_log('$this->items['.$itemId.'] was not an object', 0);
4180
            }
4181
        }
4182
4183
        if ($debug > 1) {
4184
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4185
        }
4186
4187
        return $result;
4188
    }
4189
4190
    /**
4191
     * Updates learnpath attributes to point to the previous element
4192
     * The last part is similar to set_current_item but processing the other way around.
4193
     */
4194
    public function previous()
4195
    {
4196
        $this->last = $this->get_current_item_id();
4197
        $this->items[$this->last]->save(
4198
            false,
4199
            $this->prerequisites_match($this->last)
4200
        );
4201
        $this->autocomplete_parents($this->last);
4202
        $new_index = $this->get_previous_index();
4203
        $this->index = $new_index;
4204
        $this->current = $this->ordered_items[$new_index];
4205
    }
4206
4207
    /**
4208
     * Publishes a learnpath. This basically means show or hide the learnpath
4209
     * to normal users.
4210
     * Can be used as abstract.
4211
     *
4212
     * @param int $lp_id          Learnpath ID
4213
     * @param int $set_visibility New visibility
4214
     *
4215
     * @return bool
4216
     */
4217
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4218
    {
4219
        $action = 'visible';
4220
        if ($set_visibility != 1) {
4221
            $action = 'invisible';
4222
            self::toggle_publish($lp_id, 'i');
4223
        }
4224
4225
        return api_item_property_update(
4226
            api_get_course_info(),
4227
            TOOL_LEARNPATH,
4228
            $lp_id,
4229
            $action,
4230
            api_get_user_id()
4231
        );
4232
    }
4233
4234
    /**
4235
     * Publishes a learnpath category.
4236
     * This basically means show or hide the learnpath category to normal users.
4237
     *
4238
     * @param int $id
4239
     * @param int $visibility
4240
     *
4241
     * @throws \Doctrine\ORM\NonUniqueResultException
4242
     * @throws \Doctrine\ORM\ORMException
4243
     * @throws \Doctrine\ORM\OptimisticLockException
4244
     * @throws \Doctrine\ORM\TransactionRequiredException
4245
     *
4246
     * @return bool
4247
     */
4248
    public static function toggleCategoryVisibility($id, $visibility = 1)
4249
    {
4250
        $action = 'visible';
4251
        if ($visibility != 1) {
4252
            self::toggleCategoryPublish($id, 0);
4253
            $action = 'invisible';
4254
        }
4255
4256
        return api_item_property_update(
4257
            api_get_course_info(),
4258
            TOOL_LEARNPATH_CATEGORY,
4259
            $id,
4260
            $action,
4261
            api_get_user_id()
4262
        );
4263
    }
4264
4265
    /**
4266
     * Publishes a learnpath. This basically means show or hide the learnpath
4267
     * on the course homepage
4268
     * Can be used as abstract.
4269
     *
4270
     * @param int    $lp_id          Learnpath id
4271
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4272
     *
4273
     * @return bool
4274
     */
4275
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4276
    {
4277
        $course_id = api_get_course_int_id();
4278
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4279
        $lp_id = (int) $lp_id;
4280
        $sql = "SELECT * FROM $tbl_lp
4281
                WHERE iid = $lp_id";
4282
        $result = Database::query($sql);
4283
        if (Database::num_rows($result)) {
4284
            $row = Database::fetch_array($result);
4285
            $name = Database::escape_string($row['name']);
4286
            if ($set_visibility == 'i') {
4287
                $v = 0;
4288
            }
4289
            if ($set_visibility == 'v') {
4290
                $v = 1;
4291
            }
4292
4293
            $session_id = api_get_session_id();
4294
            $session_condition = api_get_session_condition($session_id);
4295
4296
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4297
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4298
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4299
4300
            $sql = "SELECT * FROM $tbl_tool
4301
                    WHERE
4302
                        c_id = $course_id AND
4303
                        (link = '$link' OR link = '$oldLink') AND
4304
                        image = 'scormbuilder.gif' AND
4305
                        (
4306
                            link LIKE '$link%' OR
4307
                            link LIKE '$oldLink%'
4308
                        )
4309
                        $session_condition
4310
                    ";
4311
4312
            $result = Database::query($sql);
4313
            $num = Database::num_rows($result);
4314
            if ($set_visibility == 'i' && $num > 0) {
4315
                $sql = "DELETE FROM $tbl_tool
4316
                        WHERE
4317
                            c_id = $course_id AND
4318
                            (link = '$link' OR link = '$oldLink') AND
4319
                            image='scormbuilder.gif'
4320
                            $session_condition";
4321
                Database::query($sql);
4322
            } elseif ($set_visibility == 'v' && $num == 0) {
4323
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4324
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4325
                Database::query($sql);
4326
                $insertId = Database::insert_id();
4327
                if ($insertId) {
4328
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4329
                    Database::query($sql);
4330
                }
4331
            } elseif ($set_visibility == 'v' && $num > 0) {
4332
                $sql = "UPDATE $tbl_tool SET
4333
                            c_id = $course_id,
4334
                            name = '$name',
4335
                            link = '$link',
4336
                            image = 'scormbuilder.gif',
4337
                            visibility = '$v',
4338
                            admin = '0',
4339
                            address = 'pastillegris.gif',
4340
                            added_tool = 0,
4341
                            session_id = $session_id
4342
                        WHERE
4343
                            c_id = ".$course_id." AND
4344
                            (link = '$link' OR link = '$oldLink') AND
4345
                            image='scormbuilder.gif'
4346
                            $session_condition
4347
                        ";
4348
                Database::query($sql);
4349
            } else {
4350
                // Parameter and database incompatible, do nothing, exit.
4351
                return false;
4352
            }
4353
        } else {
4354
            return false;
4355
        }
4356
    }
4357
4358
    /**
4359
     * Publishes a learnpath.
4360
     * Show or hide the learnpath category on the course homepage.
4361
     *
4362
     * @param int $id
4363
     * @param int $setVisibility
4364
     *
4365
     * @throws \Doctrine\ORM\NonUniqueResultException
4366
     * @throws \Doctrine\ORM\ORMException
4367
     * @throws \Doctrine\ORM\OptimisticLockException
4368
     * @throws \Doctrine\ORM\TransactionRequiredException
4369
     *
4370
     * @return bool
4371
     */
4372
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4373
    {
4374
        $courseId = api_get_course_int_id();
4375
        $sessionId = api_get_session_id();
4376
        $sessionCondition = api_get_session_condition(
4377
            $sessionId,
4378
            true,
4379
            false,
4380
            't.sessionId'
4381
        );
4382
4383
        $em = Database::getManager();
4384
4385
        /** @var CLpCategory $category */
4386
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4387
4388
        if (!$category) {
4389
            return false;
4390
        }
4391
4392
        if (empty($courseId)) {
4393
            return false;
4394
        }
4395
4396
        $link = self::getCategoryLinkForTool($id);
4397
4398
        /** @var CTool $tool */
4399
        $tool = $em->createQuery("
4400
                SELECT t FROM ChamiloCourseBundle:CTool t
4401
                WHERE
4402
                    t.course = :course AND
4403
                    t.link = :link1 AND
4404
                    t.image LIKE 'lp_category.%' AND
4405
                    t.link LIKE :link2
4406
                    $sessionCondition
4407
            ")
4408
            ->setParameters([
4409
                'course' => $courseId,
4410
                'link1' => $link,
4411
                'link2' => "$link%",
4412
            ])
4413
            ->getOneOrNullResult();
4414
4415
        if ($setVisibility == 0 && $tool) {
4416
            $em->remove($tool);
4417
            $em->flush();
4418
4419
            return true;
4420
        }
4421
4422
        if ($setVisibility == 1 && !$tool) {
4423
            $tool = new CTool();
4424
            $tool
4425
                ->setCategory('authoring')
4426
                ->setCourse(api_get_course_entity($courseId))
4427
                ->setName(strip_tags($category->getName()))
4428
                ->setLink($link)
4429
                ->setImage('lp_category.png')
4430
                ->setVisibility(1)
4431
                ->setAdmin(0)
4432
                ->setAddress('pastillegris.gif')
4433
                ->setAddedTool(0)
4434
                ->setSessionId($sessionId)
4435
                ->setTarget('_self');
4436
4437
            $em->persist($tool);
4438
            $em->flush();
4439
4440
            $tool->setId($tool->getIid());
4441
4442
            $em->persist($tool);
4443
            $em->flush();
4444
4445
            return true;
4446
        }
4447
4448
        if ($setVisibility == 1 && $tool) {
4449
            $tool
4450
                ->setName(strip_tags($category->getName()))
4451
                ->setVisibility(1);
4452
4453
            $em->persist($tool);
4454
            $em->flush();
4455
4456
            return true;
4457
        }
4458
4459
        return false;
4460
    }
4461
4462
    /**
4463
     * Check if the learnpath category is visible for a user.
4464
     *
4465
     * @param CLpCategory $category
4466
     * @param User        $user
4467
     * @param int
4468
     * @param int
4469
     *
4470
     * @return bool
4471
     */
4472
    public static function categoryIsVisibleForStudent(
4473
        CLpCategory $category,
4474
        User $user,
4475
        $courseId = 0,
4476
        $sessionId = 0
4477
    ) {
4478
        if (empty($category)) {
4479
            return false;
4480
        }
4481
4482
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4483
4484
        if ($isAllowedToEdit) {
4485
            return true;
4486
        }
4487
4488
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4489
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4490
4491
        $courseInfo = api_get_course_info_by_id($courseId);
4492
4493
        $categoryVisibility = api_get_item_visibility(
4494
            $courseInfo,
4495
            TOOL_LEARNPATH_CATEGORY,
4496
            $category->getId(),
4497
            $sessionId
4498
        );
4499
4500
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4501
            return false;
4502
        }
4503
4504
        $subscriptionSettings = self::getSubscriptionSettings();
4505
4506
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4507
            return true;
4508
        }
4509
4510
        $users = $category->getUsers();
4511
4512
        if (empty($users) || !$users->count()) {
4513
            return true;
4514
        }
4515
4516
        if ($category->hasUserAdded($user)) {
4517
            return true;
4518
        }
4519
4520
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4521
        if (!empty($groups)) {
4522
            $em = Database::getManager();
4523
4524
            /** @var ItemPropertyRepository $itemRepo */
4525
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4526
4527
            /** @var CourseRepository $courseRepo */
4528
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4529
            $session = null;
4530
            if (!empty($sessionId)) {
4531
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4532
            }
4533
4534
            $course = $courseRepo->find($courseId);
4535
4536
            // Subscribed groups to a LP
4537
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4538
                TOOL_LEARNPATH_CATEGORY,
4539
                $category->getId(),
4540
                $course,
4541
                $session
4542
            );
4543
4544
            if (!empty($subscribedGroupsInLp)) {
4545
                $groups = array_column($groups, 'iid');
4546
                /** @var CItemProperty $item */
4547
                foreach ($subscribedGroupsInLp as $item) {
4548
                    if ($item->getGroup() &&
4549
                        in_array($item->getGroup()->getId(), $groups)
4550
                    ) {
4551
                        return true;
4552
                    }
4553
                }
4554
            }
4555
        }
4556
4557
        return false;
4558
    }
4559
4560
    /**
4561
     * Check if a learnpath category is published as course tool.
4562
     *
4563
     * @param CLpCategory $category
4564
     * @param int         $courseId
4565
     *
4566
     * @return bool
4567
     */
4568
    public static function categoryIsPublished(
4569
        CLpCategory $category,
4570
        $courseId
4571
    ) {
4572
        $link = self::getCategoryLinkForTool($category->getId());
4573
        $em = Database::getManager();
4574
4575
        $tools = $em
4576
            ->createQuery("
4577
                SELECT t FROM ChamiloCourseBundle:CTool t
4578
                WHERE t.course = :course AND 
4579
                    t.name = :name AND
4580
                    t.image LIKE 'lp_category.%' AND
4581
                    t.link LIKE :link
4582
            ")
4583
            ->setParameters([
4584
                'course' => $courseId,
4585
                'name' => strip_tags($category->getName()),
4586
                'link' => "$link%",
4587
            ])
4588
            ->getResult();
4589
4590
        /** @var CTool $tool */
4591
        $tool = current($tools);
4592
4593
        return $tool ? $tool->getVisibility() : false;
4594
    }
4595
4596
    /**
4597
     * Restart the whole learnpath. Return the URL of the first element.
4598
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4599
     * To use a similar method  statically, use the create_new_attempt() method.
4600
     *
4601
     * @return bool
4602
     */
4603
    public function restart()
4604
    {
4605
        if ($this->debug > 0) {
4606
            error_log('In learnpath::restart()', 0);
4607
        }
4608
        // TODO
4609
        // Call autosave method to save the current progress.
4610
        //$this->index = 0;
4611
        if (api_is_invitee()) {
4612
            return false;
4613
        }
4614
        $session_id = api_get_session_id();
4615
        $course_id = api_get_course_int_id();
4616
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4617
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4618
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4619
        if ($this->debug > 2) {
4620
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4621
        }
4622
        Database::query($sql);
4623
        $view_id = Database::insert_id();
4624
4625
        if ($view_id) {
4626
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4627
            Database::query($sql);
4628
            $this->lp_view_id = $view_id;
4629
            $this->attempt = $this->attempt + 1;
4630
        } else {
4631
            $this->error = 'Could not insert into item_view table...';
4632
4633
            return false;
4634
        }
4635
        $this->autocomplete_parents($this->current);
4636
        foreach ($this->items as $index => $dummy) {
4637
            $this->items[$index]->restart();
4638
            $this->items[$index]->set_lp_view($this->lp_view_id);
4639
        }
4640
        $this->first();
4641
4642
        return true;
4643
    }
4644
4645
    /**
4646
     * Saves the current item.
4647
     *
4648
     * @return bool
4649
     */
4650
    public function save_current()
4651
    {
4652
        $debug = $this->debug;
4653
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4654
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4655
        if ($debug) {
4656
            error_log('save_current() saving item '.$this->current, 0);
4657
            error_log(''.print_r($this->items, true), 0);
4658
        }
4659
        if (isset($this->items[$this->current]) &&
4660
            is_object($this->items[$this->current])
4661
        ) {
4662
            if ($debug) {
4663
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4664
            }
4665
4666
            $res = $this->items[$this->current]->save(
4667
                false,
4668
                $this->prerequisites_match($this->current)
4669
            );
4670
            $this->autocomplete_parents($this->current);
4671
            $status = $this->items[$this->current]->get_status();
4672
            $this->update_queue[$this->current] = $status;
4673
4674
            if ($debug) {
4675
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4676
            }
4677
4678
            return $res;
4679
        }
4680
4681
        return false;
4682
    }
4683
4684
    /**
4685
     * Saves the given item.
4686
     *
4687
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4688
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4689
     *
4690
     * @return bool
4691
     */
4692
    public function save_item($item_id = null, $from_outside = true)
4693
    {
4694
        $debug = $this->debug;
4695
        if ($debug) {
4696
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4697
        }
4698
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4699
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4700
        if (empty($item_id)) {
4701
            $item_id = (int) $_REQUEST['id'];
4702
        }
4703
4704
        if (empty($item_id)) {
4705
            $item_id = $this->get_current_item_id();
4706
        }
4707
        if (isset($this->items[$item_id]) &&
4708
            is_object($this->items[$item_id])
4709
        ) {
4710
            if ($debug) {
4711
                error_log('Object exists');
4712
            }
4713
4714
            // Saving the item.
4715
            $res = $this->items[$item_id]->save(
4716
                $from_outside,
4717
                $this->prerequisites_match($item_id)
4718
            );
4719
4720
            if ($debug) {
4721
                error_log('update_queue before:');
4722
                error_log(print_r($this->update_queue, 1));
4723
            }
4724
            $this->autocomplete_parents($item_id);
4725
4726
            $status = $this->items[$item_id]->get_status();
4727
            $this->update_queue[$item_id] = $status;
4728
4729
            if ($debug) {
4730
                error_log('get_status(): '.$status);
4731
                error_log('update_queue after:');
4732
                error_log(print_r($this->update_queue, 1));
4733
            }
4734
4735
            return $res;
4736
        }
4737
4738
        return false;
4739
    }
4740
4741
    /**
4742
     * Saves the last item seen's ID only in case.
4743
     */
4744
    public function save_last()
4745
    {
4746
        $course_id = api_get_course_int_id();
4747
        $debug = $this->debug;
4748
        if ($debug) {
4749
            error_log('In learnpath::save_last()', 0);
4750
        }
4751
        $session_condition = api_get_session_condition(
4752
            api_get_session_id(),
4753
            true,
4754
            false
4755
        );
4756
        $table = Database::get_course_table(TABLE_LP_VIEW);
4757
4758
        if (isset($this->current) && !api_is_invitee()) {
4759
            if ($debug) {
4760
                error_log('Saving current item ('.$this->current.') for later review', 0);
4761
            }
4762
            $sql = "UPDATE $table SET
4763
                        last_item = ".$this->get_current_item_id()."
4764
                    WHERE
4765
                        c_id = $course_id AND
4766
                        lp_id = ".$this->get_id()." AND
4767
                        user_id = ".$this->get_user_id()." ".$session_condition;
4768
4769
            if ($debug) {
4770
                error_log('Saving last item seen : '.$sql, 0);
4771
            }
4772
            Database::query($sql);
4773
        }
4774
4775
        if (!api_is_invitee()) {
4776
            // Save progress.
4777
            list($progress) = $this->get_progress_bar_text('%');
4778
            if ($progress >= 0 && $progress <= 100) {
4779
                $progress = (int) $progress;
4780
                $sql = "UPDATE $table SET
4781
                            progress = $progress
4782
                        WHERE
4783
                            c_id = $course_id AND
4784
                            lp_id = ".$this->get_id()." AND
4785
                            user_id = ".$this->get_user_id()." ".$session_condition;
4786
                // Ignore errors as some tables might not have the progress field just yet.
4787
                Database::query($sql);
4788
                $this->progress_db = $progress;
4789
            }
4790
        }
4791
    }
4792
4793
    /**
4794
     * Sets the current item ID (checks if valid and authorized first).
4795
     *
4796
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4797
     */
4798
    public function set_current_item($item_id = null)
4799
    {
4800
        $debug = $this->debug;
4801
        if ($debug) {
4802
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4803
        }
4804
        if (empty($item_id)) {
4805
            if ($debug) {
4806
                error_log('No new current item given, ignore...', 0);
4807
            }
4808
            // Do nothing.
4809
        } else {
4810
            if ($debug) {
4811
                error_log('New current item given is '.$item_id.'...', 0);
4812
            }
4813
            if (is_numeric($item_id)) {
4814
                $item_id = (int) $item_id;
4815
                // TODO: Check in database here.
4816
                $this->last = $this->current;
4817
                $this->current = $item_id;
4818
                // TODO: Update $this->index as well.
4819
                foreach ($this->ordered_items as $index => $item) {
4820
                    if ($item == $this->current) {
4821
                        $this->index = $index;
4822
                        break;
4823
                    }
4824
                }
4825
                if ($debug) {
4826
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4827
                }
4828
            } else {
4829
                if ($debug) {
4830
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4831
                }
4832
            }
4833
        }
4834
    }
4835
4836
    /**
4837
     * Sets the encoding.
4838
     *
4839
     * @param string $enc New encoding
4840
     *
4841
     * @return bool
4842
     *
4843
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4844
     */
4845
    public function set_encoding($enc = 'UTF-8')
4846
    {
4847
        $enc = api_refine_encoding_id($enc);
4848
        if (empty($enc)) {
4849
            $enc = api_get_system_encoding();
4850
        }
4851
        if (api_is_encoding_supported($enc)) {
4852
            $lp = $this->get_id();
4853
            if ($lp != 0) {
4854
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4855
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4856
                        WHERE iid = ".$lp;
4857
                $res = Database::query($sql);
4858
4859
                return $res;
4860
            }
4861
        }
4862
4863
        return false;
4864
    }
4865
4866
    /**
4867
     * Sets the JS lib setting in the database directly.
4868
     * This is the JavaScript library file this lp needs to load on startup.
4869
     *
4870
     * @param string $lib Proximity setting
4871
     *
4872
     * @return bool True on update success. False otherwise.
4873
     */
4874
    public function set_jslib($lib = '')
4875
    {
4876
        $lp = $this->get_id();
4877
4878
        if ($lp != 0) {
4879
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4880
            $lib = Database::escape_string($lib);
4881
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4882
                    WHERE iid = $lp";
4883
            $res = Database::query($sql);
4884
4885
            return $res;
4886
        }
4887
4888
        return false;
4889
    }
4890
4891
    /**
4892
     * Sets the name of the LP maker (publisher) (and save).
4893
     *
4894
     * @param string $name Optional string giving the new content_maker of this learnpath
4895
     *
4896
     * @return bool True
4897
     */
4898
    public function set_maker($name = '')
4899
    {
4900
        if (empty($name)) {
4901
            return false;
4902
        }
4903
        $this->maker = $name;
4904
        $table = Database::get_course_table(TABLE_LP_MAIN);
4905
        $lp_id = $this->get_id();
4906
        $sql = "UPDATE $table SET
4907
                content_maker = '".Database::escape_string($this->maker)."'
4908
                WHERE iid = $lp_id";
4909
        Database::query($sql);
4910
4911
        return true;
4912
    }
4913
4914
    /**
4915
     * Sets the name of the current learnpath (and save).
4916
     *
4917
     * @param string $name Optional string giving the new name of this learnpath
4918
     *
4919
     * @return bool True/False
4920
     */
4921
    public function set_name($name = null)
4922
    {
4923
        if (empty($name)) {
4924
            return false;
4925
        }
4926
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4927
        $name = Database::escape_string($name);
4928
4929
        $this->name = $name;
4930
4931
        $lp_id = $this->get_id();
4932
        $course_id = $this->course_info['real_id'];
4933
        $sql = "UPDATE $lp_table SET
4934
                name = '$name'
4935
                WHERE iid = $lp_id";
4936
        $result = Database::query($sql);
4937
        // If the lp is visible on the homepage, change his name there.
4938
        if (Database::affected_rows($result)) {
4939
            $session_id = api_get_session_id();
4940
            $session_condition = api_get_session_condition($session_id);
4941
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4942
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4943
            $sql = "UPDATE $tbl_tool SET name = '$name'
4944
            	    WHERE
4945
            	        c_id = $course_id AND
4946
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4947
            Database::query($sql);
4948
4949
            return true;
4950
        }
4951
4952
        return false;
4953
    }
4954
4955
    /**
4956
     * Set index specified prefix terms for all items in this path.
4957
     *
4958
     * @param string $terms_string Comma-separated list of terms
4959
     * @param string $prefix       Xapian term prefix
4960
     *
4961
     * @return bool False on error, true otherwise
4962
     */
4963
    public function set_terms_by_prefix($terms_string, $prefix)
4964
    {
4965
        $course_id = api_get_course_int_id();
4966
        if (api_get_setting('search_enabled') !== 'true') {
4967
            return false;
4968
        }
4969
4970
        if (!extension_loaded('xapian')) {
4971
            return false;
4972
        }
4973
4974
        $terms_string = trim($terms_string);
4975
        $terms = explode(',', $terms_string);
4976
        array_walk($terms, 'trim_value');
4977
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4978
4979
        // Don't do anything if no change, verify only at DB, not the search engine.
4980
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
4981
            return false;
4982
        }
4983
4984
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4985
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4986
4987
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4988
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4989
        $lp_id = (int) $_POST['lp_id'];
4990
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4991
        $result = Database::query($sql);
4992
        $di = new ChamiloIndexer();
4993
4994
        while ($lp_item = Database::fetch_array($result)) {
4995
            // Get search_did.
4996
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4997
            $sql = 'SELECT * FROM %s
4998
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4999
                    LIMIT 1';
5000
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5001
5002
            //echo $sql; echo '<br>';
5003
            $res = Database::query($sql);
5004
            if (Database::num_rows($res) > 0) {
5005
                $se_ref = Database::fetch_array($res);
5006
                // Compare terms.
5007
                $doc = $di->get_document($se_ref['search_did']);
5008
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5009
                $xterms = [];
5010
                foreach ($xapian_terms as $xapian_term) {
5011
                    $xterms[] = substr($xapian_term['name'], 1);
5012
                }
5013
5014
                $dterms = $terms;
5015
                $missing_terms = array_diff($dterms, $xterms);
5016
                $deprecated_terms = array_diff($xterms, $dterms);
5017
5018
                // Save it to search engine.
5019
                foreach ($missing_terms as $term) {
5020
                    $doc->add_term($prefix.$term, 1);
5021
                }
5022
                foreach ($deprecated_terms as $term) {
5023
                    $doc->remove_term($prefix.$term);
5024
                }
5025
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5026
                $di->getDb()->flush();
5027
            }
5028
        }
5029
5030
        return true;
5031
    }
5032
5033
    /**
5034
     * Sets the theme of the LP (local/remote) (and save).
5035
     *
5036
     * @param string $name Optional string giving the new theme of this learnpath
5037
     *
5038
     * @return bool Returns true if theme name is not empty
5039
     */
5040
    public function set_theme($name = '')
5041
    {
5042
        $this->theme = $name;
5043
        $table = Database::get_course_table(TABLE_LP_MAIN);
5044
        $lp_id = $this->get_id();
5045
        $sql = "UPDATE $table
5046
                SET theme = '".Database::escape_string($this->theme)."'
5047
                WHERE iid = $lp_id";
5048
        Database::query($sql);
5049
5050
        return true;
5051
    }
5052
5053
    /**
5054
     * Sets the image of an LP (and save).
5055
     *
5056
     * @param string $name Optional string giving the new image of this learnpath
5057
     *
5058
     * @return bool Returns true if theme name is not empty
5059
     */
5060
    public function set_preview_image($name = '')
5061
    {
5062
        $this->preview_image = $name;
5063
        $table = Database::get_course_table(TABLE_LP_MAIN);
5064
        $lp_id = $this->get_id();
5065
        $sql = "UPDATE $table SET
5066
                preview_image = '".Database::escape_string($this->preview_image)."'
5067
                WHERE iid = $lp_id";
5068
        Database::query($sql);
5069
5070
        return true;
5071
    }
5072
5073
    /**
5074
     * Sets the author of a LP (and save).
5075
     *
5076
     * @param string $name Optional string giving the new author of this learnpath
5077
     *
5078
     * @return bool Returns true if author's name is not empty
5079
     */
5080
    public function set_author($name = '')
5081
    {
5082
        $this->author = $name;
5083
        $table = Database::get_course_table(TABLE_LP_MAIN);
5084
        $lp_id = $this->get_id();
5085
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5086
                WHERE iid = $lp_id";
5087
        Database::query($sql);
5088
5089
        return true;
5090
    }
5091
5092
    /**
5093
     * Sets the hide_toc_frame parameter of a LP (and save).
5094
     *
5095
     * @param int $hide 1 if frame is hidden 0 then else
5096
     *
5097
     * @return bool Returns true if author's name is not empty
5098
     */
5099
    public function set_hide_toc_frame($hide)
5100
    {
5101
        if (intval($hide) == $hide) {
5102
            $this->hide_toc_frame = $hide;
5103
            $table = Database::get_course_table(TABLE_LP_MAIN);
5104
            $lp_id = $this->get_id();
5105
            $sql = "UPDATE $table SET
5106
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5107
                    WHERE iid = $lp_id";
5108
            Database::query($sql);
5109
5110
            return true;
5111
        }
5112
5113
        return false;
5114
    }
5115
5116
    /**
5117
     * Sets the prerequisite of a LP (and save).
5118
     *
5119
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5120
     *
5121
     * @return bool returns true if prerequisite is not empty
5122
     */
5123
    public function set_prerequisite($prerequisite)
5124
    {
5125
        $this->prerequisite = (int) $prerequisite;
5126
        $table = Database::get_course_table(TABLE_LP_MAIN);
5127
        $lp_id = $this->get_id();
5128
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5129
                WHERE iid = $lp_id";
5130
        Database::query($sql);
5131
5132
        return true;
5133
    }
5134
5135
    /**
5136
     * Sets the location/proximity of the LP (local/remote) (and save).
5137
     *
5138
     * @param string $name Optional string giving the new location of this learnpath
5139
     *
5140
     * @return bool True on success / False on error
5141
     */
5142
    public function set_proximity($name = '')
5143
    {
5144
        if (empty($name)) {
5145
            return false;
5146
        }
5147
5148
        $this->proximity = $name;
5149
        $table = Database::get_course_table(TABLE_LP_MAIN);
5150
        $lp_id = $this->get_id();
5151
        $sql = "UPDATE $table SET
5152
                    content_local = '".Database::escape_string($name)."'
5153
                WHERE iid = $lp_id";
5154
        Database::query($sql);
5155
5156
        return true;
5157
    }
5158
5159
    /**
5160
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5161
     *
5162
     * @param int $id DB ID of the item
5163
     */
5164
    public function set_previous_item($id)
5165
    {
5166
        if ($this->debug > 0) {
5167
            error_log('In learnpath::set_previous_item()', 0);
5168
        }
5169
        $this->last = $id;
5170
    }
5171
5172
    /**
5173
     * Sets use_max_score.
5174
     *
5175
     * @param int $use_max_score Optional string giving the new location of this learnpath
5176
     *
5177
     * @return bool True on success / False on error
5178
     */
5179
    public function set_use_max_score($use_max_score = 1)
5180
    {
5181
        $use_max_score = (int) $use_max_score;
5182
        $this->use_max_score = $use_max_score;
5183
        $table = Database::get_course_table(TABLE_LP_MAIN);
5184
        $lp_id = $this->get_id();
5185
        $sql = "UPDATE $table SET
5186
                    use_max_score = '".$this->use_max_score."'
5187
                WHERE iid = $lp_id";
5188
        Database::query($sql);
5189
5190
        return true;
5191
    }
5192
5193
    /**
5194
     * Sets and saves the expired_on date.
5195
     *
5196
     * @param string $expired_on Optional string giving the new author of this learnpath
5197
     *
5198
     * @throws \Doctrine\ORM\OptimisticLockException
5199
     *
5200
     * @return bool Returns true if author's name is not empty
5201
     */
5202
    public function set_expired_on($expired_on)
5203
    {
5204
        $em = Database::getManager();
5205
        /** @var CLp $lp */
5206
        $lp = $em
5207
            ->getRepository('ChamiloCourseBundle:CLp')
5208
            ->findOneBy(
5209
                [
5210
                    'iid' => $this->get_id(),
5211
                ]
5212
            );
5213
5214
        if (!$lp) {
5215
            return false;
5216
        }
5217
5218
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5219
5220
        $lp->setExpiredOn($this->expired_on);
5221
        $em->persist($lp);
5222
        $em->flush();
5223
5224
        return true;
5225
    }
5226
5227
    /**
5228
     * Sets and saves the publicated_on date.
5229
     *
5230
     * @param string $publicated_on Optional string giving the new author of this learnpath
5231
     *
5232
     * @throws \Doctrine\ORM\OptimisticLockException
5233
     *
5234
     * @return bool Returns true if author's name is not empty
5235
     */
5236
    public function set_publicated_on($publicated_on)
5237
    {
5238
        $em = Database::getManager();
5239
        /** @var CLp $lp */
5240
        $lp = $em
5241
            ->getRepository('ChamiloCourseBundle:CLp')
5242
            ->findOneBy(
5243
                [
5244
                    'iid' => $this->get_id(),
5245
                ]
5246
            );
5247
5248
        if (!$lp) {
5249
            return false;
5250
        }
5251
5252
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5253
        $lp->setPublicatedOn($this->publicated_on);
5254
        $em->persist($lp);
5255
        $em->flush();
5256
5257
        return true;
5258
    }
5259
5260
    /**
5261
     * Sets and saves the expired_on date.
5262
     *
5263
     * @return bool Returns true if author's name is not empty
5264
     */
5265
    public function set_modified_on()
5266
    {
5267
        $this->modified_on = api_get_utc_datetime();
5268
        $table = Database::get_course_table(TABLE_LP_MAIN);
5269
        $lp_id = $this->get_id();
5270
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5271
                WHERE iid = $lp_id";
5272
        Database::query($sql);
5273
5274
        return true;
5275
    }
5276
5277
    /**
5278
     * Sets the object's error message.
5279
     *
5280
     * @param string $error Error message. If empty, reinits the error string
5281
     */
5282
    public function set_error_msg($error = '')
5283
    {
5284
        if ($this->debug > 0) {
5285
            error_log('In learnpath::set_error_msg()', 0);
5286
        }
5287
        if (empty($error)) {
5288
            $this->error = '';
5289
        } else {
5290
            $this->error .= $error;
5291
        }
5292
    }
5293
5294
    /**
5295
     * Launches the current item if not 'sco'
5296
     * (starts timer and make sure there is a record ready in the DB).
5297
     *
5298
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5299
     *
5300
     * @return bool
5301
     */
5302
    public function start_current_item($allow_new_attempt = false)
5303
    {
5304
        $debug = $this->debug;
5305
        if ($debug) {
5306
            error_log('In learnpath::start_current_item()');
5307
            error_log('current: '.$this->current);
5308
        }
5309
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5310
            $type = $this->get_type();
5311
            $item_type = $this->items[$this->current]->get_type();
5312
            if (($type == 2 && $item_type != 'sco') ||
5313
                ($type == 3 && $item_type != 'au') ||
5314
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5315
            ) {
5316
                if ($debug) {
5317
                    error_log('item type: '.$item_type);
5318
                    error_log('lp type: '.$type);
5319
                }
5320
                $this->items[$this->current]->open($allow_new_attempt);
5321
                $this->autocomplete_parents($this->current);
5322
                $prereq_check = $this->prerequisites_match($this->current);
5323
                if ($debug) {
5324
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5325
                }
5326
                $this->items[$this->current]->save(false, $prereq_check);
5327
            }
5328
            // If sco, then it is supposed to have been updated by some other call.
5329
            if ($item_type == 'sco') {
5330
                $this->items[$this->current]->restart();
5331
            }
5332
        }
5333
        if ($debug) {
5334
            error_log('lp_view_session_id');
5335
            error_log($this->lp_view_session_id);
5336
            error_log('api session id');
5337
            error_log(api_get_session_id());
5338
            error_log('End of learnpath::start_current_item()');
5339
        }
5340
5341
        return true;
5342
    }
5343
5344
    /**
5345
     * Stops the processing and counters for the old item (as held in $this->last).
5346
     *
5347
     * @return bool True/False
5348
     */
5349
    public function stop_previous_item()
5350
    {
5351
        $debug = $this->debug;
5352
        if ($debug) {
5353
            error_log('In learnpath::stop_previous_item()', 0);
5354
        }
5355
5356
        if ($this->last != 0 && $this->last != $this->current &&
5357
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5358
        ) {
5359
            if ($debug) {
5360
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5361
            }
5362
            switch ($this->get_type()) {
5363
                case '3':
5364
                    if ($this->items[$this->last]->get_type() != 'au') {
5365
                        if ($debug) {
5366
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5367
                        }
5368
                        $this->items[$this->last]->close();
5369
                    } else {
5370
                        if ($debug) {
5371
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5372
                        }
5373
                    }
5374
                    break;
5375
                case '2':
5376
                    if ($this->items[$this->last]->get_type() != 'sco') {
5377
                        if ($debug) {
5378
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5379
                        }
5380
                        $this->items[$this->last]->close();
5381
                    } else {
5382
                        if ($debug) {
5383
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5384
                        }
5385
                    }
5386
                    break;
5387
                case '1':
5388
                default:
5389
                    if ($debug) {
5390
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5391
                    }
5392
                    $this->items[$this->last]->close();
5393
                    break;
5394
            }
5395
        } else {
5396
            if ($debug) {
5397
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5398
            }
5399
5400
            return false;
5401
        }
5402
5403
        return true;
5404
    }
5405
5406
    /**
5407
     * Updates the default view mode from fullscreen to embedded and inversely.
5408
     *
5409
     * @return string The current default view mode ('fullscreen' or 'embedded')
5410
     */
5411
    public function update_default_view_mode()
5412
    {
5413
        $table = Database::get_course_table(TABLE_LP_MAIN);
5414
        $sql = "SELECT * FROM $table
5415
                WHERE iid = ".$this->get_id();
5416
        $res = Database::query($sql);
5417
        if (Database::num_rows($res) > 0) {
5418
            $row = Database::fetch_array($res);
5419
            $default_view_mode = $row['default_view_mod'];
5420
            $view_mode = $default_view_mode;
5421
            switch ($default_view_mode) {
5422
                case 'fullscreen': // default with popup
5423
                    $view_mode = 'embedded';
5424
                    break;
5425
                case 'embedded': // default view with left menu
5426
                    $view_mode = 'embedframe';
5427
                    break;
5428
                case 'embedframe': //folded menu
5429
                    $view_mode = 'impress';
5430
                    break;
5431
                case 'impress':
5432
                    $view_mode = 'fullscreen';
5433
                    break;
5434
            }
5435
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5436
                    WHERE iid = ".$this->get_id();
5437
            Database::query($sql);
5438
            $this->mode = $view_mode;
5439
5440
            return $view_mode;
5441
        }
5442
5443
        return -1;
5444
    }
5445
5446
    /**
5447
     * Updates the default behaviour about auto-commiting SCORM updates.
5448
     *
5449
     * @return bool True if auto-commit has been set to 'on', false otherwise
5450
     */
5451
    public function update_default_scorm_commit()
5452
    {
5453
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5454
        $sql = "SELECT * FROM $lp_table
5455
                WHERE iid = ".$this->get_id();
5456
        $res = Database::query($sql);
5457
        if (Database::num_rows($res) > 0) {
5458
            $row = Database::fetch_array($res);
5459
            $force = $row['force_commit'];
5460
            if ($force == 1) {
5461
                $force = 0;
5462
                $force_return = false;
5463
            } elseif ($force == 0) {
5464
                $force = 1;
5465
                $force_return = true;
5466
            }
5467
            $sql = "UPDATE $lp_table SET force_commit = $force
5468
                    WHERE iid = ".$this->get_id();
5469
            Database::query($sql);
5470
            $this->force_commit = $force_return;
5471
5472
            return $force_return;
5473
        }
5474
5475
        return -1;
5476
    }
5477
5478
    /**
5479
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5480
     *
5481
     * @return bool True on success, false on failure
5482
     */
5483
    public function update_display_order()
5484
    {
5485
        $course_id = api_get_course_int_id();
5486
        $table = Database::get_course_table(TABLE_LP_MAIN);
5487
        $sql = "SELECT * FROM $table
5488
                WHERE c_id = $course_id
5489
                ORDER BY display_order";
5490
        $res = Database::query($sql);
5491
        if ($res === false) {
5492
            return false;
5493
        }
5494
5495
        $num = Database::num_rows($res);
5496
        // First check the order is correct, globally (might be wrong because
5497
        // of versions < 1.8.4).
5498
        if ($num > 0) {
5499
            $i = 1;
5500
            while ($row = Database::fetch_array($res)) {
5501
                if ($row['display_order'] != $i) {
5502
                    // If we find a gap in the order, we need to fix it.
5503
                    $sql = "UPDATE $table SET display_order = $i
5504
                            WHERE iid = ".$row['iid'];
5505
                    Database::query($sql);
5506
                }
5507
                $i++;
5508
            }
5509
        }
5510
5511
        return true;
5512
    }
5513
5514
    /**
5515
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5516
     *
5517
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5518
     */
5519
    public function update_reinit()
5520
    {
5521
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5522
        $sql = "SELECT * FROM $lp_table
5523
                WHERE iid = ".$this->get_id();
5524
        $res = Database::query($sql);
5525
        if (Database::num_rows($res) > 0) {
5526
            $row = Database::fetch_array($res);
5527
            $force = $row['prevent_reinit'];
5528
            if ($force == 1) {
5529
                $force = 0;
5530
            } elseif ($force == 0) {
5531
                $force = 1;
5532
            }
5533
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5534
                    WHERE iid = ".$this->get_id();
5535
            Database::query($sql);
5536
            $this->prevent_reinit = $force;
5537
5538
            return $force;
5539
        }
5540
5541
        return -1;
5542
    }
5543
5544
    /**
5545
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5546
     *
5547
     * @return string 'single', 'multi' or 'seriousgame'
5548
     *
5549
     * @author ndiechburg <[email protected]>
5550
     */
5551
    public function get_attempt_mode()
5552
    {
5553
        //Set default value for seriousgame_mode
5554
        if (!isset($this->seriousgame_mode)) {
5555
            $this->seriousgame_mode = 0;
5556
        }
5557
        // Set default value for prevent_reinit
5558
        if (!isset($this->prevent_reinit)) {
5559
            $this->prevent_reinit = 1;
5560
        }
5561
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5562
            return 'seriousgame';
5563
        }
5564
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5565
            return 'single';
5566
        }
5567
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5568
            return 'multiple';
5569
        }
5570
5571
        return 'single';
5572
    }
5573
5574
    /**
5575
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5576
     *
5577
     * @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...
5578
     *
5579
     * @return bool
5580
     *
5581
     * @author ndiechburg <[email protected]>
5582
     */
5583
    public function set_attempt_mode($mode)
5584
    {
5585
        switch ($mode) {
5586
            case 'seriousgame':
5587
                $sg_mode = 1;
5588
                $prevent_reinit = 1;
5589
                break;
5590
            case 'single':
5591
                $sg_mode = 0;
5592
                $prevent_reinit = 1;
5593
                break;
5594
            case 'multiple':
5595
                $sg_mode = 0;
5596
                $prevent_reinit = 0;
5597
                break;
5598
            default:
5599
                $sg_mode = 0;
5600
                $prevent_reinit = 0;
5601
                break;
5602
        }
5603
        $this->prevent_reinit = $prevent_reinit;
5604
        $this->seriousgame_mode = $sg_mode;
5605
        $table = Database::get_course_table(TABLE_LP_MAIN);
5606
        $sql = "UPDATE $table SET
5607
                prevent_reinit = $prevent_reinit ,
5608
                seriousgame_mode = $sg_mode
5609
                WHERE iid = ".$this->get_id();
5610
        $res = Database::query($sql);
5611
        if ($res) {
5612
            return true;
5613
        } else {
5614
            return false;
5615
        }
5616
    }
5617
5618
    /**
5619
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5620
     *
5621
     * @author ndiechburg <[email protected]>
5622
     */
5623
    public function switch_attempt_mode()
5624
    {
5625
        $mode = $this->get_attempt_mode();
5626
        switch ($mode) {
5627
            case 'single':
5628
                $next_mode = 'multiple';
5629
                break;
5630
            case 'multiple':
5631
                $next_mode = 'seriousgame';
5632
                break;
5633
            case 'seriousgame':
5634
            default:
5635
                $next_mode = 'single';
5636
                break;
5637
        }
5638
        $this->set_attempt_mode($next_mode);
5639
    }
5640
5641
    /**
5642
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5643
     * but possibility to do again a completed item.
5644
     *
5645
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5646
     *
5647
     * @author ndiechburg <[email protected]>
5648
     */
5649
    public function set_seriousgame_mode()
5650
    {
5651
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5652
        $sql = "SELECT * FROM $lp_table
5653
                WHERE iid = ".$this->get_id();
5654
        $res = Database::query($sql);
5655
        if (Database::num_rows($res) > 0) {
5656
            $row = Database::fetch_array($res);
5657
            $force = $row['seriousgame_mode'];
5658
            if ($force == 1) {
5659
                $force = 0;
5660
            } elseif ($force == 0) {
5661
                $force = 1;
5662
            }
5663
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5664
			        WHERE iid = ".$this->get_id();
5665
            Database::query($sql);
5666
            $this->seriousgame_mode = $force;
5667
5668
            return $force;
5669
        }
5670
5671
        return -1;
5672
    }
5673
5674
    /**
5675
     * Updates the "scorm_debug" value that shows or hide the debug window.
5676
     *
5677
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5678
     */
5679
    public function update_scorm_debug()
5680
    {
5681
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5682
        $sql = "SELECT * FROM $lp_table
5683
                WHERE iid = ".$this->get_id();
5684
        $res = Database::query($sql);
5685
        if (Database::num_rows($res) > 0) {
5686
            $row = Database::fetch_array($res);
5687
            $force = $row['debug'];
5688
            if ($force == 1) {
5689
                $force = 0;
5690
            } elseif ($force == 0) {
5691
                $force = 1;
5692
            }
5693
            $sql = "UPDATE $lp_table SET debug = $force
5694
                    WHERE iid = ".$this->get_id();
5695
            Database::query($sql);
5696
            $this->scorm_debug = $force;
5697
5698
            return $force;
5699
        }
5700
5701
        return -1;
5702
    }
5703
5704
    /**
5705
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5706
     *
5707
     * @author Kevin Van Den Haute
5708
     *
5709
     * @param  array
5710
     */
5711
    public function tree_array($array)
5712
    {
5713
        $array = $this->sort_tree_array($array);
5714
        $this->create_tree_array($array);
5715
    }
5716
5717
    /**
5718
     * Creates an array with the elements of the learning path tree in it.
5719
     *
5720
     * @author Kevin Van Den Haute
5721
     *
5722
     * @param array $array
5723
     * @param int   $parent
5724
     * @param int   $depth
5725
     * @param array $tmp
5726
     */
5727
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5728
    {
5729
        if (is_array($array)) {
5730
            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...
5731
                if ($array[$i]['parent_item_id'] == $parent) {
5732
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5733
                        $tmp[] = $array[$i]['parent_item_id'];
5734
                        $depth++;
5735
                    }
5736
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5737
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5738
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5739
5740
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5741
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5742
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5743
                    $this->arrMenu[] = [
5744
                        'id' => $array[$i]['id'],
5745
                        'ref' => $ref,
5746
                        'item_type' => $array[$i]['item_type'],
5747
                        'title' => $array[$i]['title'],
5748
                        'title_raw' => $array[$i]['title_raw'],
5749
                        'path' => $path,
5750
                        'description' => $array[$i]['description'],
5751
                        'parent_item_id' => $array[$i]['parent_item_id'],
5752
                        'previous_item_id' => $array[$i]['previous_item_id'],
5753
                        'next_item_id' => $array[$i]['next_item_id'],
5754
                        'min_score' => $array[$i]['min_score'],
5755
                        'max_score' => $array[$i]['max_score'],
5756
                        'mastery_score' => $array[$i]['mastery_score'],
5757
                        'display_order' => $array[$i]['display_order'],
5758
                        'prerequisite' => $preq,
5759
                        'depth' => $depth,
5760
                        'audio' => $audio,
5761
                        'prerequisite_min_score' => $prerequisiteMinScore,
5762
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5763
                    ];
5764
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5765
                }
5766
            }
5767
        }
5768
    }
5769
5770
    /**
5771
     * Sorts a multi dimensional array by parent id and display order.
5772
     *
5773
     * @author Kevin Van Den Haute
5774
     *
5775
     * @param array $array (array with al the learning path items in it)
5776
     *
5777
     * @return array
5778
     */
5779
    public function sort_tree_array($array)
5780
    {
5781
        foreach ($array as $key => $row) {
5782
            $parent[$key] = $row['parent_item_id'];
5783
            $position[$key] = $row['display_order'];
5784
        }
5785
5786
        if (count($array) > 0) {
5787
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5788
        }
5789
5790
        return $array;
5791
    }
5792
5793
    /**
5794
     * Function that creates a html list of learning path items so that we can add audio files to them.
5795
     *
5796
     * @author Kevin Van Den Haute
5797
     *
5798
     * @return string
5799
     */
5800
    public function overview()
5801
    {
5802
        $return = '';
5803
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5804
5805
        // we need to start a form when we want to update all the mp3 files
5806
        if ($update_audio == 'true') {
5807
            $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">';
5808
        }
5809
        $return .= '<div id="message"></div>';
5810
        if (count($this->items) == 0) {
5811
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5812
        } else {
5813
            $return_audio = '<table class="data_table">';
5814
            $return_audio .= '<tr>';
5815
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5816
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5817
            $return_audio .= '</tr>';
5818
5819
            if ($update_audio != 'true') {
5820
                $return .= '<div class="col-md-12">';
5821
                $return .= self::return_new_tree($update_audio);
5822
                $return .= '</div>';
5823
                $return .= Display::div(
5824
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5825
                    ['style' => 'float:left; margin-top:15px;width:100%']
5826
                );
5827
            } else {
5828
                $return_audio .= self::return_new_tree($update_audio);
5829
                $return .= $return_audio.'</table>';
5830
            }
5831
5832
            // We need to close the form when we are updating the mp3 files.
5833
            if ($update_audio == 'true') {
5834
                $return .= '<div class="footer-audio">';
5835
                $return .= Display::button(
5836
                    'save_audio',
5837
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5838
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5839
                );
5840
                $return .= '</div>';
5841
            }
5842
        }
5843
5844
        // We need to close the form when we are updating the mp3 files.
5845
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5846
            $return .= '</form>';
5847
        }
5848
5849
        return $return;
5850
    }
5851
5852
    /**
5853
     * @param string $update_audio
5854
     *
5855
     * @return array
5856
     */
5857
    public function processBuildMenuElements($update_audio = 'false')
5858
    {
5859
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5860
        $arrLP = $this->getItemsForForm();
5861
5862
        $this->tree_array($arrLP);
5863
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5864
        unset($this->arrMenu);
5865
        $default_data = null;
5866
        $default_content = null;
5867
        $elements = [];
5868
        $return_audio = null;
5869
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5870
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5871
        $countItems = count($arrLP);
5872
5873
        $upIcon = Display::return_icon(
5874
            'up.png',
5875
            get_lang('Up'),
5876
            [],
5877
            ICON_SIZE_TINY
5878
        );
5879
5880
        $disableUpIcon = Display::return_icon(
5881
            'up_na.png',
5882
            get_lang('Up'),
5883
            [],
5884
            ICON_SIZE_TINY
5885
        );
5886
5887
        $downIcon = Display::return_icon(
5888
            'down.png',
5889
            get_lang('Down'),
5890
            [],
5891
            ICON_SIZE_TINY
5892
        );
5893
5894
        $disableDownIcon = Display::return_icon(
5895
            'down_na.png',
5896
            get_lang('Down'),
5897
            [],
5898
            ICON_SIZE_TINY
5899
        );
5900
5901
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5902
5903
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5904
        $plugin = null;
5905
        if ($pluginCalendar) {
5906
            $plugin = LearningCalendarPlugin::create();
5907
        }
5908
5909
        for ($i = 0; $i < $countItems; $i++) {
5910
            $parent_id = $arrLP[$i]['parent_item_id'];
5911
            $title = $arrLP[$i]['title'];
5912
            $title_cut = $arrLP[$i]['title_raw'];
5913
            if ($show === false) {
5914
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5915
            }
5916
            // Link for the documents
5917
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5918
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5919
                $title_cut = Display::url(
5920
                    $title_cut,
5921
                    $url,
5922
                    [
5923
                        'class' => 'ajax moved',
5924
                        'data-title' => $title,
5925
                        'title' => $title,
5926
                    ]
5927
                );
5928
            }
5929
5930
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5931
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5932
                Session::write('pathItem', $arrLP[$i]['path']);
5933
            }
5934
5935
            $oddClass = 'row_even';
5936
            if (($i % 2) == 0) {
5937
                $oddClass = 'row_odd';
5938
            }
5939
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5940
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5941
5942
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5943
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5944
            } else {
5945
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5946
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5947
                } else {
5948
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5949
                        $icon = Display::return_icon('certificate.png');
5950
                    } else {
5951
                        $icon = Display::return_icon('folder_document.png');
5952
                    }
5953
                }
5954
            }
5955
5956
            // The audio column.
5957
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5958
            $audio = '';
5959
            if (!$update_audio || $update_audio != 'true') {
5960
                if (empty($arrLP[$i]['audio'])) {
5961
                    $audio .= '';
5962
                }
5963
            } else {
5964
                $types = self::getChapterTypes();
5965
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5966
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5967
                    if (!empty($arrLP[$i]['audio'])) {
5968
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5969
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
5970
                    }
5971
                }
5972
            }
5973
5974
            $return_audio .= Display::span($icon.' '.$title).
5975
                Display::tag(
5976
                    'td',
5977
                    $audio,
5978
                    ['style' => '']
5979
                );
5980
            $return_audio .= '</td>';
5981
            $move_icon = '';
5982
            $move_item_icon = '';
5983
            $edit_icon = '';
5984
            $delete_icon = '';
5985
            $audio_icon = '';
5986
            $prerequisities_icon = '';
5987
            $forumIcon = '';
5988
            $previewIcon = '';
5989
            $pluginCalendarIcon = '';
5990
            $orderIcons = '';
5991
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5992
5993
            if ($is_allowed_to_edit) {
5994
                if (!$update_audio || $update_audio != 'true') {
5995
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
5996
                        $move_icon .= '<a class="moved" href="#">';
5997
                        $move_icon .= Display::return_icon(
5998
                            'move_everywhere.png',
5999
                            get_lang('Move'),
6000
                            [],
6001
                            ICON_SIZE_TINY
6002
                        );
6003
                        $move_icon .= '</a>';
6004
                    }
6005
                }
6006
6007
                // No edit for this item types
6008
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6009
                    if ($arrLP[$i]['item_type'] != 'dir') {
6010
                        $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">';
6011
                        $edit_icon .= Display::return_icon(
6012
                            'edit.png',
6013
                            get_lang('LearnpathEditModule'),
6014
                            [],
6015
                            ICON_SIZE_TINY
6016
                        );
6017
                        $edit_icon .= '</a>';
6018
6019
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6020
                            $forumThread = null;
6021
                            if (isset($this->items[$arrLP[$i]['id']])) {
6022
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6023
                                    $this->course_int_id,
6024
                                    $this->lp_session_id
6025
                                );
6026
                            }
6027
                            if ($forumThread) {
6028
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6029
                                        'action' => 'dissociate_forum',
6030
                                        'id' => $arrLP[$i]['id'],
6031
                                        'lp_id' => $this->lp_id,
6032
                                    ]);
6033
                                $forumIcon = Display::url(
6034
                                    Display::return_icon(
6035
                                        'forum.png',
6036
                                        get_lang('DissociateForumToLPItem'),
6037
                                        [],
6038
                                        ICON_SIZE_TINY
6039
                                    ),
6040
                                    $forumIconUrl,
6041
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6042
                                );
6043
                            } else {
6044
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6045
                                        'action' => 'create_forum',
6046
                                        'id' => $arrLP[$i]['id'],
6047
                                        'lp_id' => $this->lp_id,
6048
                                    ]);
6049
                                $forumIcon = Display::url(
6050
                                    Display::return_icon(
6051
                                        'forum.png',
6052
                                        get_lang('AssociateForumToLPItem'),
6053
                                        [],
6054
                                        ICON_SIZE_TINY
6055
                                    ),
6056
                                    $forumIconUrl,
6057
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6058
                                );
6059
                            }
6060
                        }
6061
                    } else {
6062
                        $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">';
6063
                        $edit_icon .= Display::return_icon(
6064
                            'edit.png',
6065
                            get_lang('LearnpathEditModule'),
6066
                            [],
6067
                            ICON_SIZE_TINY
6068
                        );
6069
                        $edit_icon .= '</a>';
6070
                    }
6071
                } else {
6072
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6073
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6074
                        $edit_icon .= Display::return_icon(
6075
                            'edit.png',
6076
                            get_lang('Edit'),
6077
                            [],
6078
                            ICON_SIZE_TINY
6079
                        );
6080
                        $edit_icon .= '</a>';
6081
                    }
6082
                }
6083
6084
                if ($pluginCalendar) {
6085
                    $pluginLink = $pluginUrl.
6086
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6087
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6088
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6089
                    if ($itemInfo && $itemInfo['value'] == 1) {
6090
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6091
                    }
6092
                    $pluginCalendarIcon = Display::url(
6093
                        $iconCalendar,
6094
                        $pluginLink,
6095
                        ['class' => 'btn btn-default']
6096
                    );
6097
                }
6098
6099
                if ($arrLP[$i]['item_type'] != 'final_item') {
6100
                    $orderIcons = Display::url(
6101
                        $upIcon,
6102
                        'javascript:void(0)',
6103
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6104
                    );
6105
                    $orderIcons .= Display::url(
6106
                        $downIcon,
6107
                        'javascript:void(0)',
6108
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6109
                    );
6110
                }
6111
6112
                $delete_icon .= ' <a 
6113
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6114
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6115
                    class="btn btn-default">';
6116
                $delete_icon .= Display::return_icon(
6117
                    'delete.png',
6118
                    get_lang('LearnpathDeleteModule'),
6119
                    [],
6120
                    ICON_SIZE_TINY
6121
                );
6122
                $delete_icon .= '</a>';
6123
6124
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6125
                $previewImage = Display::return_icon(
6126
                    'preview_view.png',
6127
                    get_lang('Preview'),
6128
                    [],
6129
                    ICON_SIZE_TINY
6130
                );
6131
6132
                switch ($arrLP[$i]['item_type']) {
6133
                    case TOOL_DOCUMENT:
6134
                    case TOOL_LP_FINAL_ITEM:
6135
                    case TOOL_READOUT_TEXT:
6136
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6137
                        $previewIcon = Display::url(
6138
                            $previewImage,
6139
                            $urlPreviewLink,
6140
                            [
6141
                                'target' => '_blank',
6142
                                'class' => 'btn btn-default',
6143
                                'data-title' => $arrLP[$i]['title'],
6144
                                'title' => $arrLP[$i]['title'],
6145
                            ]
6146
                        );
6147
                        break;
6148
                    case TOOL_THREAD:
6149
                    case TOOL_FORUM:
6150
                    case TOOL_QUIZ:
6151
                    case TOOL_STUDENTPUBLICATION:
6152
                    case TOOL_LP_FINAL_ITEM:
6153
                    case TOOL_LINK:
6154
                        $class = 'btn btn-default';
6155
                        $target = '_blank';
6156
                        $link = self::rl_get_resource_link_for_learnpath(
6157
                            $this->course_int_id,
6158
                            $this->lp_id,
6159
                            $arrLP[$i]['id'],
6160
                            0
6161
                        );
6162
                        $previewIcon = Display::url(
6163
                            $previewImage,
6164
                            $link,
6165
                            [
6166
                                'class' => $class,
6167
                                'data-title' => $arrLP[$i]['title'],
6168
                                'title' => $arrLP[$i]['title'],
6169
                                'target' => $target,
6170
                            ]
6171
                        );
6172
                        break;
6173
                    default:
6174
                        $previewIcon = Display::url(
6175
                            $previewImage,
6176
                            $url.'&action=view_item',
6177
                            ['class' => 'btn btn-default', 'target' => '_blank']
6178
                        );
6179
                        break;
6180
                }
6181
6182
                if ($arrLP[$i]['item_type'] != 'dir') {
6183
                    $prerequisities_icon = Display::url(
6184
                        Display::return_icon(
6185
                            'accept.png',
6186
                            get_lang('LearnpathPrerequisites'),
6187
                            [],
6188
                            ICON_SIZE_TINY
6189
                        ),
6190
                        $url.'&action=edit_item_prereq',
6191
                        ['class' => 'btn btn-default']
6192
                    );
6193
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6194
                        $move_item_icon = Display::url(
6195
                            Display::return_icon(
6196
                                'move.png',
6197
                                get_lang('Move'),
6198
                                [],
6199
                                ICON_SIZE_TINY
6200
                            ),
6201
                            $url.'&action=move_item',
6202
                            ['class' => 'btn btn-default']
6203
                        );
6204
                    }
6205
                    $audio_icon = Display::url(
6206
                        Display::return_icon(
6207
                            'audio.png',
6208
                            get_lang('UplUpload'),
6209
                            [],
6210
                            ICON_SIZE_TINY
6211
                        ),
6212
                        $url.'&action=add_audio',
6213
                        ['class' => 'btn btn-default']
6214
                    );
6215
                }
6216
            }
6217
            if ($update_audio != 'true') {
6218
                $row = $move_icon.' '.$icon.
6219
                    Display::span($title_cut).
6220
                    Display::tag(
6221
                        'div',
6222
                        "<div class=\"btn-group btn-group-xs\">
6223
                                    $previewIcon 
6224
                                    $audio 
6225
                                    $edit_icon 
6226
                                    $pluginCalendarIcon
6227
                                    $forumIcon 
6228
                                    $prerequisities_icon 
6229
                                    $move_item_icon 
6230
                                    $audio_icon 
6231
                                    $orderIcons
6232
                                    $delete_icon
6233
                                </div>",
6234
                        ['class' => 'btn-toolbar button_actions']
6235
                    );
6236
            } else {
6237
                $row =
6238
                    Display::span($title.$icon).
6239
                    Display::span($audio, ['class' => 'button_actions']);
6240
            }
6241
6242
            $default_data[$arrLP[$i]['id']] = $row;
6243
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6244
6245
            if (empty($parent_id)) {
6246
                $elements[$arrLP[$i]['id']]['data'] = $row;
6247
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6248
            } else {
6249
                $parent_arrays = [];
6250
                if ($arrLP[$i]['depth'] > 1) {
6251
                    // Getting list of parents
6252
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6253
                        foreach ($arrLP as $item) {
6254
                            if ($item['id'] == $parent_id) {
6255
                                if ($item['parent_item_id'] == 0) {
6256
                                    $parent_id = $item['id'];
6257
                                    break;
6258
                                } else {
6259
                                    $parent_id = $item['parent_item_id'];
6260
                                    if (empty($parent_arrays)) {
6261
                                        $parent_arrays[] = intval($item['id']);
6262
                                    }
6263
                                    $parent_arrays[] = $parent_id;
6264
                                    break;
6265
                                }
6266
                            }
6267
                        }
6268
                    }
6269
                }
6270
6271
                if (!empty($parent_arrays)) {
6272
                    $parent_arrays = array_reverse($parent_arrays);
6273
                    $val = '$elements';
6274
                    $x = 0;
6275
                    foreach ($parent_arrays as $item) {
6276
                        if ($x != count($parent_arrays) - 1) {
6277
                            $val .= '["'.$item.'"]["children"]';
6278
                        } else {
6279
                            $val .= '["'.$item.'"]["children"]';
6280
                        }
6281
                        $x++;
6282
                    }
6283
                    $val .= "";
6284
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6285
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6286
                } else {
6287
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6288
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6289
                }
6290
            }
6291
        }
6292
6293
        return [
6294
            'elements' => $elements,
6295
            'default_data' => $default_data,
6296
            'default_content' => $default_content,
6297
            'return_audio' => $return_audio,
6298
        ];
6299
    }
6300
6301
    /**
6302
     * @param string $updateAudio true/false strings
6303
     *
6304
     * @return string
6305
     */
6306
    public function returnLpItemList($updateAudio)
6307
    {
6308
        $result = $this->processBuildMenuElements($updateAudio);
6309
6310
        $html = self::print_recursive(
6311
            $result['elements'],
6312
            $result['default_data'],
6313
            $result['default_content']
6314
        );
6315
6316
        if (!empty($html)) {
6317
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6318
        }
6319
6320
        return $html;
6321
    }
6322
6323
    /**
6324
     * @param string $update_audio
6325
     * @param bool   $drop_element_here
6326
     *
6327
     * @return string
6328
     */
6329
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6330
    {
6331
        $result = $this->processBuildMenuElements($update_audio);
6332
6333
        $list = '<ul id="lp_item_list">';
6334
        $tree = $this->print_recursive(
6335
            $result['elements'],
6336
            $result['default_data'],
6337
            $result['default_content']
6338
        );
6339
6340
        if (!empty($tree)) {
6341
            $list .= $tree;
6342
        } else {
6343
            if ($drop_element_here) {
6344
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6345
            }
6346
        }
6347
        $list .= '</ul>';
6348
6349
        $return = Display::panelCollapse(
6350
            $this->name,
6351
            $list,
6352
            'scorm-list',
6353
            null,
6354
            'scorm-list-accordion',
6355
            'scorm-list-collapse'
6356
        );
6357
6358
        if ($update_audio === 'true') {
6359
            $return = $result['return_audio'];
6360
        }
6361
6362
        return $return;
6363
    }
6364
6365
    /**
6366
     * @param array $elements
6367
     * @param array $default_data
6368
     * @param array $default_content
6369
     *
6370
     * @return string
6371
     */
6372
    public function print_recursive($elements, $default_data, $default_content)
6373
    {
6374
        $return = '';
6375
        foreach ($elements as $key => $item) {
6376
            if (isset($item['load_data']) || empty($item['data'])) {
6377
                $item['data'] = $default_data[$item['load_data']];
6378
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6379
            }
6380
            $sub_list = '';
6381
            if (isset($item['type']) && $item['type'] === 'dir') {
6382
                // empty value
6383
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6384
            }
6385
            if (empty($item['children'])) {
6386
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6387
                $active = null;
6388
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6389
                    $active = 'active';
6390
                }
6391
                $return .= Display::tag(
6392
                    'li',
6393
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6394
                    ['id' => $key, 'class' => 'record li_container']
6395
                );
6396
            } else {
6397
                // Sections
6398
                $data = '';
6399
                if (isset($item['children'])) {
6400
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6401
                }
6402
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6403
                $return .= Display::tag(
6404
                    'li',
6405
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6406
                    ['id' => $key, 'class' => 'record li_container']
6407
                );
6408
            }
6409
        }
6410
6411
        return $return;
6412
    }
6413
6414
    /**
6415
     * This function builds the action menu.
6416
     *
6417
     * @param bool $returnContent          Optional
6418
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6419
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6420
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6421
     *
6422
     * @return string
6423
     */
6424
    public function build_action_menu(
6425
        $returnContent = false,
6426
        $showRequirementButtons = true,
6427
        $isConfigPage = false,
6428
        $allowExpand = true
6429
    ) {
6430
        $actionsRight = '';
6431
        $actionsLeft = Display::url(
6432
            Display::return_icon(
6433
                'back.png',
6434
                get_lang('ReturnToLearningPaths'),
6435
                '',
6436
                ICON_SIZE_MEDIUM
6437
            ),
6438
            'lp_controller.php?'.api_get_cidreq()
6439
        );
6440
        $actionsLeft .= Display::url(
6441
            Display::return_icon(
6442
                'preview_view.png',
6443
                get_lang('Preview'),
6444
                '',
6445
                ICON_SIZE_MEDIUM
6446
            ),
6447
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6448
                'action' => 'view',
6449
                'lp_id' => $this->lp_id,
6450
                'isStudentView' => 'true',
6451
            ])
6452
        );
6453
6454
        $actionsLeft .= Display::url(
6455
            Display::return_icon(
6456
                'upload_audio.png',
6457
                get_lang('UpdateAllAudioFragments'),
6458
                '',
6459
                ICON_SIZE_MEDIUM
6460
            ),
6461
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6462
                'action' => 'admin_view',
6463
                'lp_id' => $this->lp_id,
6464
                'updateaudio' => 'true',
6465
            ])
6466
        );
6467
6468
        $subscriptionSettings = self::getSubscriptionSettings();
6469
6470
        $request = api_request_uri();
6471
        if (strpos($request, 'edit') === false) {
6472
            $actionsLeft .= Display::url(
6473
                Display::return_icon(
6474
                    'settings.png',
6475
                    get_lang('CourseSettings'),
6476
                    '',
6477
                    ICON_SIZE_MEDIUM
6478
                ),
6479
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6480
                    'action' => 'edit',
6481
                    'lp_id' => $this->lp_id,
6482
                ])
6483
            );
6484
        }
6485
6486
        if (strpos($request, 'build') === false && strpos($request, 'add_item') === false) {
6487
            $actionsLeft .= Display::url(
6488
                Display::return_icon(
6489
                    'edit.png',
6490
                    get_lang('Edit'),
6491
                    '',
6492
                    ICON_SIZE_MEDIUM
6493
                ),
6494
                'lp_controller.php?'.http_build_query([
6495
                    'action' => 'build',
6496
                    'lp_id' => $this->lp_id,
6497
                ]).'&'.api_get_cidreq()
6498
            );
6499
        }
6500
6501
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6502
            if ($this->subscribeUsers == 1 &&
6503
                $subscriptionSettings['allow_add_users_to_lp']) {
6504
                $actionsLeft .= Display::url(
6505
                    Display::return_icon(
6506
                        'user.png',
6507
                        get_lang('SubscribeUsersToLp'),
6508
                        '',
6509
                        ICON_SIZE_MEDIUM
6510
                    ),
6511
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$this->lp_id."&".api_get_cidreq()
6512
                );
6513
            }
6514
        }
6515
6516
        if ($allowExpand) {
6517
            $actionsLeft .= Display::url(
6518
                Display::return_icon(
6519
                    'expand.png',
6520
                    get_lang('Expand'),
6521
                    ['id' => 'expand'],
6522
                    ICON_SIZE_MEDIUM
6523
                ).
6524
                Display::return_icon(
6525
                    'contract.png',
6526
                    get_lang('Collapse'),
6527
                    ['id' => 'contract', 'class' => 'hide'],
6528
                    ICON_SIZE_MEDIUM
6529
                ),
6530
                '#',
6531
                ['role' => 'button', 'id' => 'hide_bar_template']
6532
            );
6533
        }
6534
6535
        if ($showRequirementButtons) {
6536
            $buttons = [
6537
                [
6538
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6539
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6540
                        'action' => 'set_previous_step_as_prerequisite',
6541
                        'lp_id' => $this->lp_id,
6542
                    ]),
6543
                ],
6544
                [
6545
                    'title' => get_lang('ClearAllPrerequisites'),
6546
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6547
                        'action' => 'clear_prerequisites',
6548
                        'lp_id' => $this->lp_id,
6549
                    ]),
6550
                ],
6551
            ];
6552
            $actionsRight = Display::groupButtonWithDropDown(
6553
                get_lang('PrerequisitesOptions'),
6554
                $buttons,
6555
                true
6556
            );
6557
        }
6558
6559
        $toolbar = Display::toolbarAction(
6560
            'actions-lp-controller',
6561
            [$actionsLeft, $actionsRight]
6562
        );
6563
6564
        if ($returnContent) {
6565
            return $toolbar;
6566
        }
6567
6568
        echo $toolbar;
6569
    }
6570
6571
    /**
6572
     * Creates the default learning path folder.
6573
     *
6574
     * @param array $course
6575
     * @param int   $creatorId
6576
     *
6577
     * @return bool
6578
     */
6579
    public static function generate_learning_path_folder($course, $creatorId = 0)
6580
    {
6581
        // Creating learning_path folder
6582
        $dir = '/learning_path';
6583
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6584
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6585
6586
        $folder = false;
6587
        $folderData = create_unexisting_directory(
6588
            $course,
6589
            $creatorId,
6590
            0,
6591
            null,
6592
            0,
6593
            $filepath,
6594
            $dir,
6595
            get_lang('LearningPaths'),
6596
            0
6597
        );
6598
6599
        if (!empty($folderData)) {
6600
            $folder = true;
6601
        }
6602
6603
        return $folder;
6604
    }
6605
6606
    /**
6607
     * @param array  $course
6608
     * @param string $lp_name
6609
     * @param int    $creatorId
6610
     *
6611
     * @return array
6612
     */
6613
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6614
    {
6615
        $filepath = '';
6616
        $dir = '/learning_path/';
6617
6618
        if (empty($lp_name)) {
6619
            $lp_name = $this->name;
6620
        }
6621
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6622
        $folder = self::generate_learning_path_folder($course, $creatorId);
6623
6624
        // Limits title size
6625
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6626
        $dir = $dir.$title;
6627
6628
        // Creating LP folder
6629
        $documentId = null;
6630
        if ($folder) {
6631
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6632
            $folderData = create_unexisting_directory(
6633
                $course,
6634
                $creatorId,
6635
                0,
6636
                0,
6637
                0,
6638
                $filepath,
6639
                $dir,
6640
                $lp_name
6641
            );
6642
            if (!empty($folderData)) {
6643
                $folder = true;
6644
            }
6645
6646
            $documentId = $folderData->getIid();
6647
            $dir = $dir.'/';
6648
            if ($folder) {
6649
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6650
            }
6651
        }
6652
6653
        if (empty($documentId)) {
6654
            $dir = api_remove_trailing_slash($dir);
6655
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6656
        }
6657
6658
        $array = [
6659
            'dir' => $dir,
6660
            'filepath' => $filepath,
6661
            'folder' => $folder,
6662
            'id' => $documentId,
6663
        ];
6664
6665
        return $array;
6666
    }
6667
6668
    /**
6669
     * Create a new document //still needs some finetuning.
6670
     *
6671
     * @param array  $courseInfo
6672
     * @param string $content
6673
     * @param string $title
6674
     * @param string $extension
6675
     * @param int    $parentId
6676
     * @param int    $creatorId  creator id
6677
     *
6678
     * @return int
6679
     */
6680
    public function create_document(
6681
        $courseInfo,
6682
        $content = '',
6683
        $title = '',
6684
        $extension = 'html',
6685
        $parentId = 0,
6686
        $creatorId = 0
6687
    ) {
6688
        if (!empty($courseInfo)) {
6689
            $course_id = $courseInfo['real_id'];
6690
        } else {
6691
            $course_id = api_get_course_int_id();
6692
        }
6693
6694
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6695
        $sessionId = api_get_session_id();
6696
6697
        // Generates folder
6698
        $result = $this->generate_lp_folder($courseInfo);
6699
        $dir = $result['dir'];
6700
6701
        if (empty($parentId) || $parentId == '/') {
6702
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6703
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6704
6705
            if ($parentId === '/') {
6706
                $dir = '/';
6707
            }
6708
6709
            // Please, do not modify this dirname formatting.
6710
            if (strstr($dir, '..')) {
6711
                $dir = '/';
6712
            }
6713
6714
            if (!empty($dir[0]) && $dir[0] == '.') {
6715
                $dir = substr($dir, 1);
6716
            }
6717
            if (!empty($dir[0]) && $dir[0] != '/') {
6718
                $dir = '/'.$dir;
6719
            }
6720
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6721
                $dir .= '/';
6722
            }
6723
        } else {
6724
            $parentInfo = DocumentManager::get_document_data_by_id(
6725
                $parentId,
6726
                $courseInfo['code']
6727
            );
6728
            if (!empty($parentInfo)) {
6729
                $dir = $parentInfo['path'].'/';
6730
            }
6731
        }
6732
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6733
        // is already escaped twice when it gets here.
6734
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6735
        if (!empty($title)) {
6736
            $title = api_replace_dangerous_char(stripslashes($title));
6737
        } else {
6738
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6739
        }
6740
6741
        $title = disable_dangerous_file($title);
6742
        $filename = $title;
6743
        $content = !empty($content) ? $content : $_POST['content_lp'];
6744
        $tmp_filename = $filename;
6745
        $filename = $tmp_filename.'.'.$extension;
6746
6747
        if ($extension === 'html') {
6748
            $content = stripslashes($content);
6749
            $content = str_replace(
6750
                api_get_path(WEB_COURSE_PATH),
6751
                api_get_path(REL_PATH).'courses/',
6752
                $content
6753
            );
6754
6755
            // Change the path of mp3 to absolute.
6756
            // The first regexp deals with :// urls.
6757
            $content = preg_replace(
6758
                "|(flashvars=\"file=)([^:/]+)/|",
6759
                "$1".api_get_path(
6760
                    REL_COURSE_PATH
6761
                ).$courseInfo['path'].'/document/',
6762
                $content
6763
            );
6764
            // The second regexp deals with audio/ urls.
6765
            $content = preg_replace(
6766
                "|(flashvars=\"file=)([^/]+)/|",
6767
                "$1".api_get_path(
6768
                    REL_COURSE_PATH
6769
                ).$courseInfo['path'].'/document/$2/',
6770
                $content
6771
            );
6772
            // For flv player: To prevent edition problem with firefox,
6773
            // we have to use a strange tip (don't blame me please).
6774
            $content = str_replace(
6775
                '</body>',
6776
                '<style type="text/css">body{}</style></body>',
6777
                $content
6778
            );
6779
        }
6780
6781
        $save_file_path = $dir.$filename;
6782
6783
        $document = DocumentManager::addDocument(
6784
            $courseInfo,
6785
            $save_file_path,
6786
            'file',
6787
            '',
6788
            $tmp_filename,
6789
            '',
6790
            0, //readonly
6791
            true,
6792
            null,
6793
            $sessionId,
6794
            $creatorId,
6795
            false,
6796
            $content,
6797
            $parentId
6798
        );
6799
6800
        $document_id = $document->getIid();
6801
        if ($document_id) {
6802
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6803
            $new_title = $originalTitle;
6804
6805
            if ($new_comment || $new_title) {
6806
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6807
                $ct = '';
6808
                if ($new_comment) {
6809
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6810
                }
6811
                if ($new_title) {
6812
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6813
                }
6814
6815
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6816
                        WHERE c_id = $course_id AND id = $document_id ";
6817
                Database::query($sql);
6818
            }
6819
        }
6820
6821
        return $document_id;
6822
    }
6823
6824
    /**
6825
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6826
     *
6827
     * @param array $_course array
6828
     */
6829
    public function edit_document($_course)
6830
    {
6831
        $course_id = api_get_course_int_id();
6832
        $urlAppend = api_get_configuration_value('url_append');
6833
        // Please, do not modify this dirname formatting.
6834
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6835
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6836
6837
        if (strstr($dir, '..')) {
6838
            $dir = '/';
6839
        }
6840
6841
        if (isset($dir[0]) && $dir[0] == '.') {
6842
            $dir = substr($dir, 1);
6843
        }
6844
6845
        if (isset($dir[0]) && $dir[0] != '/') {
6846
            $dir = '/'.$dir;
6847
        }
6848
6849
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6850
            $dir .= '/';
6851
        }
6852
6853
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6854
        if (!is_dir($filepath)) {
6855
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6856
        }
6857
6858
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6859
6860
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6861
            $document_id = (int) $_POST['path'];
6862
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6863
            if (empty($documentInfo)) {
6864
                // Try with iid
6865
                $table = Database::get_course_table(TABLE_DOCUMENT);
6866
                $sql = "SELECT id, path FROM $table 
6867
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6868
                $res_doc = Database::query($sql);
6869
                $row = Database::fetch_array($res_doc);
6870
                if ($row) {
6871
                    $document_id = $row['id'];
6872
                    $documentPath = $row['path'];
6873
                }
6874
            } else {
6875
                $documentPath = $documentInfo['path'];
6876
            }
6877
6878
            $content = stripslashes($_POST['content_lp']);
6879
            $file = $filepath.$documentPath;
6880
6881
            if (!file_exists($file)) {
6882
                return false;
6883
            }
6884
6885
            if ($fp = @fopen($file, 'w')) {
6886
                $content = str_replace(
6887
                    api_get_path(WEB_COURSE_PATH),
6888
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6889
                    $content
6890
                );
6891
                // Change the path of mp3 to absolute.
6892
                // The first regexp deals with :// urls.
6893
                $content = preg_replace(
6894
                    "|(flashvars=\"file=)([^:/]+)/|",
6895
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6896
                    $content
6897
                );
6898
                // The second regexp deals with audio/ urls.
6899
                $content = preg_replace(
6900
                    "|(flashvars=\"file=)([^:/]+)/|",
6901
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6902
                    $content
6903
                );
6904
                fputs($fp, $content);
6905
                fclose($fp);
6906
6907
                $sql = "UPDATE $table_doc SET
6908
                            title='".Database::escape_string($_POST['title'])."'
6909
                        WHERE c_id = $course_id AND id = ".$document_id;
6910
                Database::query($sql);
6911
            }
6912
        }
6913
    }
6914
6915
    /**
6916
     * Displays the selected item, with a panel for manipulating the item.
6917
     *
6918
     * @param int    $item_id
6919
     * @param string $msg
6920
     * @param bool   $show_actions
6921
     *
6922
     * @return string
6923
     */
6924
    public function display_item($item_id, $msg = null, $show_actions = true)
6925
    {
6926
        $course_id = api_get_course_int_id();
6927
        $return = '';
6928
        if (is_numeric($item_id)) {
6929
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6930
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6931
                    WHERE lp.iid = ".intval($item_id);
6932
            $result = Database::query($sql);
6933
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6934
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6935
6936
                // Prevents wrong parent selection for document, see Bug#1251.
6937
                if ($row['item_type'] != 'dir') {
6938
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6939
                }
6940
6941
                if ($show_actions) {
6942
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6943
                }
6944
                $return .= '<div style="padding:10px;">';
6945
6946
                if ($msg != '') {
6947
                    $return .= $msg;
6948
                }
6949
6950
                $return .= '<h3>'.$row['title'].'</h3>';
6951
6952
                switch ($row['item_type']) {
6953
                    case TOOL_THREAD:
6954
                        $link = $this->rl_get_resource_link_for_learnpath(
6955
                            $course_id,
6956
                            $row['lp_id'],
6957
                            $item_id,
6958
                            0
6959
                        );
6960
                        $return .= Display::url(
6961
                            get_lang('GoToThread'),
6962
                            $link,
6963
                            ['class' => 'btn btn-primary']
6964
                        );
6965
                        break;
6966
                    case TOOL_FORUM:
6967
                        $return .= Display::url(
6968
                            get_lang('GoToForum'),
6969
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6970
                            ['class' => 'btn btn-primary']
6971
                        );
6972
                        break;
6973
                    case TOOL_QUIZ:
6974
                        if (!empty($row['path'])) {
6975
                            $exercise = new Exercise();
6976
                            $exercise->read($row['path']);
6977
                            $return .= $exercise->description.'<br />';
6978
                            $return .= Display::url(
6979
                                get_lang('GoToExercise'),
6980
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6981
                                ['class' => 'btn btn-primary']
6982
                            );
6983
                        }
6984
                        break;
6985
                    case TOOL_LP_FINAL_ITEM:
6986
                        $return .= $this->getSavedFinalItem();
6987
                        break;
6988
                    case TOOL_DOCUMENT:
6989
                    case TOOL_READOUT_TEXT:
6990
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6991
                        $sql_doc = "SELECT path FROM $tbl_doc
6992
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
6993
                        $result = Database::query($sql_doc);
6994
                        $path_file = Database::result($result, 0, 0);
6995
                        $path_parts = pathinfo($path_file);
6996
                        // TODO: Correct the following naive comparisons.
6997
                        if (in_array($path_parts['extension'], [
6998
                            'html',
6999
                            'txt',
7000
                            'png',
7001
                            'jpg',
7002
                            'JPG',
7003
                            'jpeg',
7004
                            'JPEG',
7005
                            'gif',
7006
                            'swf',
7007
                            'pdf',
7008
                            'htm',
7009
                        ])) {
7010
                            $return .= $this->display_document($row['path'], true, true);
7011
                        }
7012
                        break;
7013
                    case TOOL_HOTPOTATOES:
7014
                        $return .= $this->display_document($row['path'], false, true);
7015
                        break;
7016
                }
7017
                $return .= '</div>';
7018
            }
7019
        }
7020
7021
        return $return;
7022
    }
7023
7024
    /**
7025
     * Shows the needed forms for editing a specific item.
7026
     *
7027
     * @param int $item_id
7028
     *
7029
     * @throws Exception
7030
     * @throws HTML_QuickForm_Error
7031
     *
7032
     * @return string
7033
     */
7034
    public function display_edit_item($item_id)
7035
    {
7036
        $course_id = api_get_course_int_id();
7037
        $return = '';
7038
        $item_id = (int) $item_id;
7039
7040
        if (empty($item_id)) {
7041
            return '';
7042
        }
7043
7044
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7045
        $sql = "SELECT * FROM $tbl_lp_item
7046
                WHERE iid = ".$item_id;
7047
        $res = Database::query($sql);
7048
        $row = Database::fetch_array($res);
7049
        switch ($row['item_type']) {
7050
            case 'dir':
7051
            case 'asset':
7052
            case 'sco':
7053
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7054
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7055
                    $return .= $this->display_item_form(
7056
                        $row['item_type'],
7057
                        get_lang('EditCurrentChapter').' :',
7058
                        'edit',
7059
                        $item_id,
7060
                        $row
7061
                    );
7062
                } else {
7063
                    $return .= $this->display_item_form(
7064
                        $row['item_type'],
7065
                        get_lang('EditCurrentChapter').' :',
7066
                        'edit_item',
7067
                        $item_id,
7068
                        $row
7069
                    );
7070
                }
7071
                break;
7072
            case TOOL_DOCUMENT:
7073
            case TOOL_READOUT_TEXT:
7074
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7075
                $sql = "SELECT lp.*, doc.path as dir
7076
                        FROM $tbl_lp_item as lp
7077
                        LEFT JOIN $tbl_doc as doc
7078
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7079
                        WHERE
7080
                            doc.c_id = $course_id AND
7081
                            lp.iid = ".$item_id;
7082
                $res_step = Database::query($sql);
7083
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7084
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7085
7086
                if ($row['item_type'] === TOOL_DOCUMENT) {
7087
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7088
                }
7089
7090
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7091
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7092
                }
7093
                break;
7094
            case TOOL_LINK:
7095
                $linkId = (int) $row['path'];
7096
                if (!empty($linkId)) {
7097
                    $table = Database::get_course_table(TABLE_LINK);
7098
                    $sql = 'SELECT url FROM '.$table.'
7099
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7100
                    $res_link = Database::query($sql);
7101
                    $row_link = Database::fetch_array($res_link);
7102
                    if (empty($row_link)) {
7103
                        // Try with id
7104
                        $sql = 'SELECT url FROM '.$table.'
7105
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7106
                        $res_link = Database::query($sql);
7107
                        $row_link = Database::fetch_array($res_link);
7108
                    }
7109
7110
                    if (is_array($row_link)) {
7111
                        $row['url'] = $row_link['url'];
7112
                    }
7113
                }
7114
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7115
                $return .= $this->display_link_form('edit', $item_id, $row);
7116
                break;
7117
            case TOOL_LP_FINAL_ITEM:
7118
                Session::write('finalItem', true);
7119
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7120
                $sql = "SELECT lp.*, doc.path as dir
7121
                        FROM $tbl_lp_item as lp
7122
                        LEFT JOIN $tbl_doc as doc
7123
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7124
                        WHERE
7125
                            doc.c_id = $course_id AND
7126
                            lp.iid = ".$item_id;
7127
                $res_step = Database::query($sql);
7128
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7129
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7130
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7131
                break;
7132
            case TOOL_QUIZ:
7133
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7134
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7135
                break;
7136
            case TOOL_HOTPOTATOES:
7137
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7138
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7139
                break;
7140
            case TOOL_STUDENTPUBLICATION:
7141
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7142
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7143
                break;
7144
            case TOOL_FORUM:
7145
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7146
                $return .= $this->display_forum_form('edit', $item_id, $row);
7147
                break;
7148
            case TOOL_THREAD:
7149
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7150
                $return .= $this->display_thread_form('edit', $item_id, $row);
7151
                break;
7152
        }
7153
7154
        return $return;
7155
    }
7156
7157
    /**
7158
     * Function that displays a list with al the resources that
7159
     * could be added to the learning path.
7160
     *
7161
     * @throws Exception
7162
     * @throws HTML_QuickForm_Error
7163
     *
7164
     * @return bool
7165
     */
7166
    public function display_resources()
7167
    {
7168
        $course_code = api_get_course_id();
7169
7170
        // Get all the docs.
7171
        $documents = $this->get_documents(true);
7172
7173
        // Get all the exercises.
7174
        $exercises = $this->get_exercises();
7175
7176
        // Get all the links.
7177
        $links = $this->get_links();
7178
7179
        // Get all the student publications.
7180
        $works = $this->get_student_publications();
7181
7182
        // Get all the forums.
7183
        $forums = $this->get_forums(null, $course_code);
7184
7185
        // Get the final item form (see BT#11048) .
7186
        $finish = $this->getFinalItemForm();
7187
7188
        $headers = [
7189
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7190
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7191
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7192
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7193
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7194
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7195
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7196
        ];
7197
7198
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7199
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7200
7201
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7202
7203
        echo Display::tabs(
7204
            $headers,
7205
            [
7206
                $documents,
7207
                $exercises,
7208
                $links,
7209
                $works,
7210
                $forums,
7211
                $dir,
7212
                $finish,
7213
            ],
7214
            'resource_tab',
7215
            [],
7216
            [],
7217
            $selected
7218
        );
7219
7220
        return true;
7221
    }
7222
7223
    /**
7224
     * Returns the extension of a document.
7225
     *
7226
     * @param string $filename
7227
     *
7228
     * @return string Extension (part after the last dot)
7229
     */
7230
    public function get_extension($filename)
7231
    {
7232
        $explode = explode('.', $filename);
7233
7234
        return $explode[count($explode) - 1];
7235
    }
7236
7237
    /**
7238
     * Displays a document by id.
7239
     *
7240
     * @param int  $id
7241
     * @param bool $show_title
7242
     * @param bool $iframe
7243
     * @param bool $edit_link
7244
     *
7245
     * @return string
7246
     */
7247
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7248
    {
7249
        $_course = api_get_course_info();
7250
        $course_id = api_get_course_int_id();
7251
        $id = (int) $id;
7252
        $return = '';
7253
        $table = Database::get_course_table(TABLE_DOCUMENT);
7254
        $sql_doc = "SELECT * FROM $table
7255
                    WHERE c_id = $course_id AND iid = $id";
7256
        $res_doc = Database::query($sql_doc);
7257
        $row_doc = Database::fetch_array($res_doc);
7258
7259
        // TODO: Add a path filter.
7260
        if ($iframe) {
7261
            $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>';
7262
        } else {
7263
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7264
        }
7265
7266
        return $return;
7267
    }
7268
7269
    /**
7270
     * Return HTML form to add/edit a quiz.
7271
     *
7272
     * @param string $action     Action (add/edit)
7273
     * @param int    $id         Item ID if already exists
7274
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7275
     *
7276
     * @throws Exception
7277
     *
7278
     * @return string HTML form
7279
     */
7280
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7281
    {
7282
        $course_id = api_get_course_int_id();
7283
        $id = (int) $id;
7284
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7285
7286
        if ($id != 0 && is_array($extra_info)) {
7287
            $item_title = $extra_info['title'];
7288
            $item_description = $extra_info['description'];
7289
        } elseif (is_numeric($extra_info)) {
7290
            $sql = "SELECT title, description
7291
                    FROM $tbl_quiz
7292
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7293
7294
            $result = Database::query($sql);
7295
            $row = Database::fetch_array($result);
7296
            $item_title = $row['title'];
7297
            $item_description = $row['description'];
7298
        } else {
7299
            $item_title = '';
7300
            $item_description = '';
7301
        }
7302
        $item_title = Security::remove_XSS($item_title);
7303
        $item_description = Security::remove_XSS($item_description);
7304
7305
        $parent = 0;
7306
        if ($id != 0 && is_array($extra_info)) {
7307
            $parent = $extra_info['parent_item_id'];
7308
        }
7309
7310
        $arrLP = $this->getItemsForForm();
7311
        $this->tree_array($arrLP);
7312
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7313
        unset($this->arrMenu);
7314
7315
        $form = new FormValidator(
7316
            'quiz_form',
7317
            'POST',
7318
            $this->getCurrentBuildingModeURL()
7319
        );
7320
        $defaults = [];
7321
7322
        if ($action === 'add') {
7323
            $legend = get_lang('CreateTheExercise');
7324
        } elseif ($action === 'move') {
7325
            $legend = get_lang('MoveTheCurrentExercise');
7326
        } else {
7327
            $legend = get_lang('EditCurrentExecice');
7328
        }
7329
7330
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7331
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7332
        }
7333
7334
        $form->addHeader($legend);
7335
7336
        if ($action != 'move') {
7337
            $this->setItemTitle($form);
7338
            $defaults['title'] = $item_title;
7339
        }
7340
7341
        // Select for Parent item, root or chapter
7342
        $selectParent = $form->addSelect(
7343
            'parent',
7344
            get_lang('Parent'),
7345
            [],
7346
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7347
        );
7348
        $selectParent->addOption($this->name, 0);
7349
7350
        $arrHide = [
7351
            $id,
7352
        ];
7353
        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...
7354
            if ($action != 'add') {
7355
                if (
7356
                    ($arrLP[$i]['item_type'] == 'dir') &&
7357
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7358
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7359
                ) {
7360
                    $selectParent->addOption(
7361
                        $arrLP[$i]['title'],
7362
                        $arrLP[$i]['id'],
7363
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7364
                    );
7365
7366
                    if ($parent == $arrLP[$i]['id']) {
7367
                        $selectParent->setSelected($arrLP[$i]['id']);
7368
                    }
7369
                } else {
7370
                    $arrHide[] = $arrLP[$i]['id'];
7371
                }
7372
            } else {
7373
                if ($arrLP[$i]['item_type'] == 'dir') {
7374
                    $selectParent->addOption(
7375
                        $arrLP[$i]['title'],
7376
                        $arrLP[$i]['id'],
7377
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7378
                    );
7379
7380
                    if ($parent == $arrLP[$i]['id']) {
7381
                        $selectParent->setSelected($arrLP[$i]['id']);
7382
                    }
7383
                }
7384
            }
7385
        }
7386
7387
        if (is_array($arrLP)) {
7388
            reset($arrLP);
7389
        }
7390
7391
        $selectPrevious = $form->addSelect(
7392
            'previous',
7393
            get_lang('Position'),
7394
            [],
7395
            ['id' => 'previous']
7396
        );
7397
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7398
7399
        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...
7400
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7401
                $arrLP[$i]['id'] != $id
7402
            ) {
7403
                $selectPrevious->addOption(
7404
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7405
                    $arrLP[$i]['id']
7406
                );
7407
7408
                if (is_array($extra_info)) {
7409
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7410
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7411
                    }
7412
                } elseif ($action == 'add') {
7413
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7414
                }
7415
            }
7416
        }
7417
7418
        if ($action != 'move') {
7419
            $arrHide = [];
7420
            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...
7421
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7422
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7423
                }
7424
            }
7425
        }
7426
7427
        if ($action === 'add') {
7428
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7429
        } else {
7430
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7431
        }
7432
7433
        if ($action === 'move') {
7434
            $form->addHidden('title', $item_title);
7435
            $form->addHidden('description', $item_description);
7436
        }
7437
7438
        if (is_numeric($extra_info)) {
7439
            $form->addHidden('path', $extra_info);
7440
        } elseif (is_array($extra_info)) {
7441
            $form->addHidden('path', $extra_info['path']);
7442
        }
7443
7444
        $form->addHidden('type', TOOL_QUIZ);
7445
        $form->addHidden('post_time', time());
7446
        $form->setDefaults($defaults);
7447
7448
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7449
    }
7450
7451
    /**
7452
     * Addition of Hotpotatoes tests.
7453
     *
7454
     * @param string $action
7455
     * @param int    $id         Internal ID of the item
7456
     * @param string $extra_info
7457
     *
7458
     * @return string HTML structure to display the hotpotatoes addition formular
7459
     */
7460
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7461
    {
7462
        $course_id = api_get_course_int_id();
7463
        $uploadPath = DIR_HOTPOTATOES;
7464
7465
        if ($id != 0 && is_array($extra_info)) {
7466
            $item_title = stripslashes($extra_info['title']);
7467
            $item_description = stripslashes($extra_info['description']);
7468
        } elseif (is_numeric($extra_info)) {
7469
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7470
7471
            $sql = "SELECT * FROM $TBL_DOCUMENT
7472
                    WHERE
7473
                        c_id = $course_id AND
7474
                        path LIKE '".$uploadPath."/%/%htm%' AND
7475
                        iid = ".(int) $extra_info."
7476
                    ORDER BY iid ASC";
7477
7478
            $res_hot = Database::query($sql);
7479
            $row = Database::fetch_array($res_hot);
7480
7481
            $item_title = $row['title'];
7482
            $item_description = $row['description'];
7483
7484
            if (!empty($row['comment'])) {
7485
                $item_title = $row['comment'];
7486
            }
7487
        } else {
7488
            $item_title = '';
7489
            $item_description = '';
7490
        }
7491
7492
        $parent = 0;
7493
        if ($id != 0 && is_array($extra_info)) {
7494
            $parent = $extra_info['parent_item_id'];
7495
        }
7496
7497
        $arrLP = $this->getItemsForForm();
7498
        $legend = '<legend>';
7499
        if ($action == 'add') {
7500
            $legend .= get_lang('CreateTheExercise');
7501
        } elseif ($action == 'move') {
7502
            $legend .= get_lang('MoveTheCurrentExercise');
7503
        } else {
7504
            $legend .= get_lang('EditCurrentExecice');
7505
        }
7506
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7507
            $legend .= Display:: return_message(
7508
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7509
            );
7510
        }
7511
        $legend .= '</legend>';
7512
7513
        $return = '<form method="POST">';
7514
        $return .= $legend;
7515
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7516
        $return .= '<tr>';
7517
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7518
        $return .= '<td class="input">';
7519
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7520
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7521
        $arrHide = [$id];
7522
7523
        if (count($arrLP) > 0) {
7524
            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...
7525
                if ($action != 'add') {
7526
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7527
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7528
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7529
                    ) {
7530
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7531
                    } else {
7532
                        $arrHide[] = $arrLP[$i]['id'];
7533
                    }
7534
                } else {
7535
                    if ($arrLP[$i]['item_type'] == 'dir') {
7536
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7537
                    }
7538
                }
7539
            }
7540
            reset($arrLP);
7541
        }
7542
7543
        $return .= '</select>';
7544
        $return .= '</td>';
7545
        $return .= '</tr>';
7546
        $return .= '<tr>';
7547
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7548
        $return .= '<td class="input">';
7549
        $return .= '<select id="previous" name="previous" size="1">';
7550
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7551
7552
        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...
7553
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7554
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7555
                    $selected = 'selected="selected" ';
7556
                } elseif ($action == 'add') {
7557
                    $selected = 'selected="selected" ';
7558
                } else {
7559
                    $selected = '';
7560
                }
7561
7562
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7563
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7564
            }
7565
        }
7566
7567
        $return .= '</select>';
7568
        $return .= '</td>';
7569
        $return .= '</tr>';
7570
7571
        if ($action != 'move') {
7572
            $return .= '<tr>';
7573
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7574
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7575
            $return .= '</tr>';
7576
            $id_prerequisite = 0;
7577
            if (is_array($arrLP) && count($arrLP) > 0) {
7578
                foreach ($arrLP as $key => $value) {
7579
                    if ($value['id'] == $id) {
7580
                        $id_prerequisite = $value['prerequisite'];
7581
                        break;
7582
                    }
7583
                }
7584
7585
                $arrHide = [];
7586
                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...
7587
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7588
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7589
                    }
7590
                }
7591
            }
7592
        }
7593
7594
        $return .= '<tr>';
7595
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7596
            get_lang('SaveHotpotatoes').'</button></td>';
7597
        $return .= '</tr>';
7598
        $return .= '</table>';
7599
7600
        if ($action == 'move') {
7601
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7602
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7603
        }
7604
7605
        if (is_numeric($extra_info)) {
7606
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7607
        } elseif (is_array($extra_info)) {
7608
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7609
        }
7610
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7611
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7612
        $return .= '</form>';
7613
7614
        return $return;
7615
    }
7616
7617
    /**
7618
     * Return the form to display the forum edit/add option.
7619
     *
7620
     * @param string $action
7621
     * @param int    $id         ID of the lp_item if already exists
7622
     * @param string $extra_info
7623
     *
7624
     * @throws Exception
7625
     *
7626
     * @return string HTML form
7627
     */
7628
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7629
    {
7630
        $course_id = api_get_course_int_id();
7631
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7632
7633
        $item_title = '';
7634
        $item_description = '';
7635
7636
        if ($id != 0 && is_array($extra_info)) {
7637
            $item_title = stripslashes($extra_info['title']);
7638
        } elseif (is_numeric($extra_info)) {
7639
            $sql = "SELECT forum_title as title, forum_comment as comment
7640
                    FROM $tbl_forum
7641
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7642
7643
            $result = Database::query($sql);
7644
            $row = Database::fetch_array($result);
7645
7646
            $item_title = $row['title'];
7647
            $item_description = $row['comment'];
7648
        }
7649
        $parent = 0;
7650
        if ($id != 0 && is_array($extra_info)) {
7651
            $parent = $extra_info['parent_item_id'];
7652
        }
7653
        $arrLP = $this->getItemsForForm();
7654
        $this->tree_array($arrLP);
7655
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7656
        unset($this->arrMenu);
7657
7658
        if ($action == 'add') {
7659
            $legend = get_lang('CreateTheForum');
7660
        } elseif ($action == 'move') {
7661
            $legend = get_lang('MoveTheCurrentForum');
7662
        } else {
7663
            $legend = get_lang('EditCurrentForum');
7664
        }
7665
7666
        $form = new FormValidator(
7667
            'forum_form',
7668
            'POST',
7669
            $this->getCurrentBuildingModeURL()
7670
        );
7671
        $defaults = [];
7672
7673
        $form->addHeader($legend);
7674
7675
        if ($action != 'move') {
7676
            $this->setItemTitle($form);
7677
            $defaults['title'] = $item_title;
7678
        }
7679
7680
        $selectParent = $form->addSelect(
7681
            'parent',
7682
            get_lang('Parent'),
7683
            [],
7684
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7685
        );
7686
        $selectParent->addOption($this->name, 0);
7687
        $arrHide = [
7688
            $id,
7689
        ];
7690
        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...
7691
            if ($action != 'add') {
7692
                if ($arrLP[$i]['item_type'] == 'dir' &&
7693
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7694
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7695
                ) {
7696
                    $selectParent->addOption(
7697
                        $arrLP[$i]['title'],
7698
                        $arrLP[$i]['id'],
7699
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7700
                    );
7701
7702
                    if ($parent == $arrLP[$i]['id']) {
7703
                        $selectParent->setSelected($arrLP[$i]['id']);
7704
                    }
7705
                } else {
7706
                    $arrHide[] = $arrLP[$i]['id'];
7707
                }
7708
            } else {
7709
                if ($arrLP[$i]['item_type'] == 'dir') {
7710
                    $selectParent->addOption(
7711
                        $arrLP[$i]['title'],
7712
                        $arrLP[$i]['id'],
7713
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7714
                    );
7715
7716
                    if ($parent == $arrLP[$i]['id']) {
7717
                        $selectParent->setSelected($arrLP[$i]['id']);
7718
                    }
7719
                }
7720
            }
7721
        }
7722
7723
        if (is_array($arrLP)) {
7724
            reset($arrLP);
7725
        }
7726
7727
        $selectPrevious = $form->addSelect(
7728
            'previous',
7729
            get_lang('Position'),
7730
            [],
7731
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7732
        );
7733
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7734
7735
        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...
7736
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7737
                $arrLP[$i]['id'] != $id
7738
            ) {
7739
                $selectPrevious->addOption(
7740
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7741
                    $arrLP[$i]['id']
7742
                );
7743
7744
                if (isset($extra_info['previous_item_id']) &&
7745
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7746
                ) {
7747
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7748
                } elseif ($action == 'add') {
7749
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7750
                }
7751
            }
7752
        }
7753
7754
        if ($action != 'move') {
7755
            $id_prerequisite = 0;
7756
            if (is_array($arrLP)) {
7757
                foreach ($arrLP as $key => $value) {
7758
                    if ($value['id'] == $id) {
7759
                        $id_prerequisite = $value['prerequisite'];
7760
                        break;
7761
                    }
7762
                }
7763
            }
7764
7765
            $arrHide = [];
7766
            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...
7767
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7768
                    if (isset($extra_info['previous_item_id']) &&
7769
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7770
                    ) {
7771
                        $s_selected_position = $arrLP[$i]['id'];
7772
                    } elseif ($action == 'add') {
7773
                        $s_selected_position = 0;
7774
                    }
7775
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7776
                }
7777
            }
7778
        }
7779
7780
        if ($action == 'add') {
7781
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
7782
        } else {
7783
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
7784
        }
7785
7786
        if ($action == 'move') {
7787
            $form->addHidden('title', $item_title);
7788
            $form->addHidden('description', $item_description);
7789
        }
7790
7791
        if (is_numeric($extra_info)) {
7792
            $form->addHidden('path', $extra_info);
7793
        } elseif (is_array($extra_info)) {
7794
            $form->addHidden('path', $extra_info['path']);
7795
        }
7796
        $form->addHidden('type', TOOL_FORUM);
7797
        $form->addHidden('post_time', time());
7798
        $form->setDefaults($defaults);
7799
7800
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7801
    }
7802
7803
    /**
7804
     * Return HTML form to add/edit forum threads.
7805
     *
7806
     * @param string $action
7807
     * @param int    $id         Item ID if already exists in learning path
7808
     * @param string $extra_info
7809
     *
7810
     * @throws Exception
7811
     *
7812
     * @return string HTML form
7813
     */
7814
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7815
    {
7816
        $course_id = api_get_course_int_id();
7817
        if (empty($course_id)) {
7818
            return null;
7819
        }
7820
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7821
7822
        $item_title = '';
7823
        $item_description = '';
7824
        if ($id != 0 && is_array($extra_info)) {
7825
            $item_title = stripslashes($extra_info['title']);
7826
        } elseif (is_numeric($extra_info)) {
7827
            $sql = "SELECT thread_title as title FROM $tbl_forum
7828
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7829
7830
            $result = Database::query($sql);
7831
            $row = Database::fetch_array($result);
7832
7833
            $item_title = $row['title'];
7834
            $item_description = '';
7835
        }
7836
7837
        $parent = 0;
7838
        if ($id != 0 && is_array($extra_info)) {
7839
            $parent = $extra_info['parent_item_id'];
7840
        }
7841
7842
        $arrLP = $this->getItemsForForm();
7843
        $this->tree_array($arrLP);
7844
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7845
        unset($this->arrMenu);
7846
7847
        $form = new FormValidator(
7848
            'thread_form',
7849
            'POST',
7850
            $this->getCurrentBuildingModeURL()
7851
        );
7852
        $defaults = [];
7853
7854
        if ($action == 'add') {
7855
            $legend = get_lang('CreateTheForum');
7856
        } elseif ($action == 'move') {
7857
            $legend = get_lang('MoveTheCurrentForum');
7858
        } else {
7859
            $legend = get_lang('EditCurrentForum');
7860
        }
7861
7862
        $form->addHeader($legend);
7863
        $selectParent = $form->addSelect(
7864
            'parent',
7865
            get_lang('Parent'),
7866
            [],
7867
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7868
        );
7869
        $selectParent->addOption($this->name, 0);
7870
7871
        $arrHide = [
7872
            $id,
7873
        ];
7874
7875
        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...
7876
            if ($action != 'add') {
7877
                if (
7878
                    ($arrLP[$i]['item_type'] == 'dir') &&
7879
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7880
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7881
                ) {
7882
                    $selectParent->addOption(
7883
                        $arrLP[$i]['title'],
7884
                        $arrLP[$i]['id'],
7885
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7886
                    );
7887
7888
                    if ($parent == $arrLP[$i]['id']) {
7889
                        $selectParent->setSelected($arrLP[$i]['id']);
7890
                    }
7891
                } else {
7892
                    $arrHide[] = $arrLP[$i]['id'];
7893
                }
7894
            } else {
7895
                if ($arrLP[$i]['item_type'] == 'dir') {
7896
                    $selectParent->addOption(
7897
                        $arrLP[$i]['title'],
7898
                        $arrLP[$i]['id'],
7899
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7900
                    );
7901
7902
                    if ($parent == $arrLP[$i]['id']) {
7903
                        $selectParent->setSelected($arrLP[$i]['id']);
7904
                    }
7905
                }
7906
            }
7907
        }
7908
7909
        if ($arrLP != null) {
7910
            reset($arrLP);
7911
        }
7912
7913
        $selectPrevious = $form->addSelect(
7914
            'previous',
7915
            get_lang('Position'),
7916
            [],
7917
            ['id' => 'previous']
7918
        );
7919
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7920
7921
        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...
7922
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7923
                $selectPrevious->addOption(
7924
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7925
                    $arrLP[$i]['id']
7926
                );
7927
7928
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7929
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7930
                } elseif ($action == 'add') {
7931
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7932
                }
7933
            }
7934
        }
7935
7936
        if ($action != 'move') {
7937
            $this->setItemTitle($form);
7938
            $defaults['title'] = $item_title;
7939
7940
            $id_prerequisite = 0;
7941
            if ($arrLP != null) {
7942
                foreach ($arrLP as $key => $value) {
7943
                    if ($value['id'] == $id) {
7944
                        $id_prerequisite = $value['prerequisite'];
7945
                        break;
7946
                    }
7947
                }
7948
            }
7949
7950
            $arrHide = [];
7951
            $s_selected_position = 0;
7952
            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...
7953
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7954
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7955
                        $s_selected_position = $arrLP[$i]['id'];
7956
                    } elseif ($action == 'add') {
7957
                        $s_selected_position = 0;
7958
                    }
7959
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7960
                }
7961
            }
7962
7963
            $selectPrerequisites = $form->addSelect(
7964
                'prerequisites',
7965
                get_lang('LearnpathPrerequisites'),
7966
                [],
7967
                ['id' => 'prerequisites']
7968
            );
7969
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
7970
7971
            foreach ($arrHide as $key => $value) {
7972
                $selectPrerequisites->addOption($value['value'], $key);
7973
7974
                if ($key == $s_selected_position && $action == 'add') {
7975
                    $selectPrerequisites->setSelected($key);
7976
                } elseif ($key == $id_prerequisite && $action == 'edit') {
7977
                    $selectPrerequisites->setSelected($key);
7978
                }
7979
            }
7980
        }
7981
7982
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
7983
7984
        if ($action == 'move') {
7985
            $form->addHidden('title', $item_title);
7986
            $form->addHidden('description', $item_description);
7987
        }
7988
7989
        if (is_numeric($extra_info)) {
7990
            $form->addHidden('path', $extra_info);
7991
        } elseif (is_array($extra_info)) {
7992
            $form->addHidden('path', $extra_info['path']);
7993
        }
7994
7995
        $form->addHidden('type', TOOL_THREAD);
7996
        $form->addHidden('post_time', time());
7997
        $form->setDefaults($defaults);
7998
7999
        return $form->returnForm();
8000
    }
8001
8002
    /**
8003
     * Return the HTML form to display an item (generally a dir item).
8004
     *
8005
     * @param string $item_type
8006
     * @param string $title
8007
     * @param string $action
8008
     * @param int    $id
8009
     * @param string $extra_info
8010
     *
8011
     * @throws Exception
8012
     * @throws HTML_QuickForm_Error
8013
     *
8014
     * @return string HTML form
8015
     */
8016
    public function display_item_form(
8017
        $item_type,
8018
        $title = '',
8019
        $action = 'add_item',
8020
        $id = 0,
8021
        $extra_info = 'new'
8022
    ) {
8023
        $_course = api_get_course_info();
8024
8025
        global $charset;
8026
8027
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8028
        $item_title = '';
8029
        $item_description = '';
8030
        $item_path_fck = '';
8031
8032
        if ($id != 0 && is_array($extra_info)) {
8033
            $item_title = $extra_info['title'];
8034
            $item_description = $extra_info['description'];
8035
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8036
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8037
        }
8038
        $parent = 0;
8039
        if ($id != 0 && is_array($extra_info)) {
8040
            $parent = $extra_info['parent_item_id'];
8041
        }
8042
8043
        $id = (int) $id;
8044
        $sql = "SELECT * FROM $tbl_lp_item
8045
                WHERE
8046
                    lp_id = ".$this->lp_id." AND
8047
                    iid != $id";
8048
8049
        if ($item_type == 'dir') {
8050
            $sql .= " AND parent_item_id = 0";
8051
        }
8052
8053
        $result = Database::query($sql);
8054
        $arrLP = [];
8055
        while ($row = Database::fetch_array($result)) {
8056
            $arrLP[] = [
8057
                'id' => $row['iid'],
8058
                'item_type' => $row['item_type'],
8059
                'title' => $this->cleanItemTitle($row['title']),
8060
                'title_raw' => $row['title'],
8061
                'path' => $row['path'],
8062
                'description' => $row['description'],
8063
                'parent_item_id' => $row['parent_item_id'],
8064
                'previous_item_id' => $row['previous_item_id'],
8065
                'next_item_id' => $row['next_item_id'],
8066
                'max_score' => $row['max_score'],
8067
                'min_score' => $row['min_score'],
8068
                'mastery_score' => $row['mastery_score'],
8069
                'prerequisite' => $row['prerequisite'],
8070
                'display_order' => $row['display_order'],
8071
            ];
8072
        }
8073
8074
        $this->tree_array($arrLP);
8075
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8076
        unset($this->arrMenu);
8077
8078
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8079
8080
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8081
        $defaults['title'] = api_html_entity_decode(
8082
            $item_title,
8083
            ENT_QUOTES,
8084
            $charset
8085
        );
8086
        $defaults['description'] = $item_description;
8087
8088
        $form->addHeader($title);
8089
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8090
        $arrHide[0]['padding'] = 20;
8091
        $charset = api_get_system_encoding();
8092
        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...
8093
            if ($action != 'add') {
8094
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8095
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8096
                ) {
8097
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8098
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8099
                    if ($parent == $arrLP[$i]['id']) {
8100
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8101
                    }
8102
                }
8103
            } else {
8104
                if ($arrLP[$i]['item_type'] === 'dir') {
8105
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8106
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8107
                    if ($parent == $arrLP[$i]['id']) {
8108
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8109
                    }
8110
                }
8111
            }
8112
        }
8113
8114
        if ($action != 'move') {
8115
            $this->setItemTitle($form);
8116
        } else {
8117
            $form->addElement('hidden', 'title');
8118
        }
8119
8120
        $parentSelect = $form->addElement(
8121
            'select',
8122
            'parent',
8123
            get_lang('Parent'),
8124
            '',
8125
            [
8126
                'id' => 'idParent',
8127
                'onchange' => 'javascript: load_cbo(this.value);',
8128
            ]
8129
        );
8130
8131
        foreach ($arrHide as $key => $value) {
8132
            $parentSelect->addOption(
8133
                $value['value'],
8134
                $key,
8135
                'style="padding-left:'.$value['padding'].'px;"'
8136
            );
8137
            $lastPosition = $key;
8138
        }
8139
8140
        if (!empty($s_selected_parent)) {
8141
            $parentSelect->setSelected($s_selected_parent);
8142
        }
8143
8144
        if (is_array($arrLP)) {
8145
            reset($arrLP);
8146
        }
8147
8148
        $arrHide = [];
8149
        // POSITION
8150
        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...
8151
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8152
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8153
                //this is the same!
8154
                if (isset($extra_info['previous_item_id']) &&
8155
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8156
                ) {
8157
                    $s_selected_position = $arrLP[$i]['id'];
8158
                } elseif ($action == 'add') {
8159
                    $s_selected_position = $arrLP[$i]['id'];
8160
                }
8161
8162
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8163
            }
8164
        }
8165
8166
        $position = $form->addElement(
8167
            'select',
8168
            'previous',
8169
            get_lang('Position'),
8170
            '',
8171
            ['id' => 'previous']
8172
        );
8173
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8174
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8175
8176
        $lastPosition = null;
8177
        foreach ($arrHide as $key => $value) {
8178
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8179
            $lastPosition = $key;
8180
        }
8181
8182
        if (!empty($s_selected_position)) {
8183
            $position->setSelected($s_selected_position);
8184
        }
8185
8186
        // When new chapter add at the end
8187
        if ($action === 'add_item') {
8188
            $position->setSelected($lastPosition);
8189
        }
8190
8191
        if (is_array($arrLP)) {
8192
            reset($arrLP);
8193
        }
8194
8195
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8196
8197
        //fix in order to use the tab
8198
        if ($item_type === 'dir') {
8199
            $form->addElement('hidden', 'type', 'dir');
8200
        }
8201
8202
        $extension = null;
8203
        if (!empty($item_path)) {
8204
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8205
        }
8206
8207
        //assets can't be modified
8208
        //$item_type == 'asset' ||
8209
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8210
            if ($item_type == 'sco') {
8211
                $form->addElement(
8212
                    'html',
8213
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8214
                );
8215
            }
8216
            $renderer = $form->defaultRenderer();
8217
            $renderer->setElementTemplate(
8218
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8219
                'content_lp'
8220
            );
8221
8222
            $relative_prefix = '';
8223
            $editor_config = [
8224
                'ToolbarSet' => 'LearningPathDocuments',
8225
                'Width' => '100%',
8226
                'Height' => '500',
8227
                'FullPage' => true,
8228
                'CreateDocumentDir' => $relative_prefix,
8229
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8230
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8231
            ];
8232
8233
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8234
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8235
            $defaults['content_lp'] = file_get_contents($content_path);
8236
        }
8237
8238
        if (!empty($id)) {
8239
            $form->addHidden('id', $id);
8240
        }
8241
8242
        $form->addElement('hidden', 'type', $item_type);
8243
        $form->addElement('hidden', 'post_time', time());
8244
        $form->setDefaults($defaults);
8245
8246
        return $form->returnForm();
8247
    }
8248
8249
    /**
8250
     * @return string
8251
     */
8252
    public function getCurrentBuildingModeURL()
8253
    {
8254
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8255
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8256
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8257
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8258
8259
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8260
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8261
8262
        return $currentUrl;
8263
    }
8264
8265
    /**
8266
     * Returns the form to update or create a document.
8267
     *
8268
     * @param string $action     (add/edit)
8269
     * @param int    $id         ID of the lp_item (if already exists)
8270
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8271
     *
8272
     * @throws Exception
8273
     * @throws HTML_QuickForm_Error
8274
     *
8275
     * @return string HTML form
8276
     */
8277
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8278
    {
8279
        $course_id = api_get_course_int_id();
8280
        $_course = api_get_course_info();
8281
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8282
8283
        $no_display_edit_textarea = false;
8284
        $item_description = '';
8285
        //If action==edit document
8286
        //We don't display the document form if it's not an editable document (html or txt file)
8287
        if ($action === 'edit') {
8288
            if (is_array($extra_info)) {
8289
                $path_parts = pathinfo($extra_info['dir']);
8290
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8291
                    $no_display_edit_textarea = true;
8292
                }
8293
            }
8294
        }
8295
        $no_display_add = false;
8296
8297
        // If action==add an existing document
8298
        // We don't display the document form if it's not an editable document (html or txt file).
8299
        if ($action === 'add') {
8300
            if (is_numeric($extra_info)) {
8301
                $extra_info = (int) $extra_info;
8302
                $sql_doc = "SELECT path FROM $tbl_doc 
8303
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8304
                $result = Database::query($sql_doc);
8305
                $path_file = Database::result($result, 0, 0);
8306
                $path_parts = pathinfo($path_file);
8307
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8308
                    $no_display_add = true;
8309
                }
8310
            }
8311
        }
8312
8313
        $item_title = '';
8314
        $item_description = '';
8315
        if ($id != 0 && is_array($extra_info)) {
8316
            $item_title = stripslashes($extra_info['title']);
8317
            $item_description = stripslashes($extra_info['description']);
8318
            if (empty($item_title)) {
8319
                $path_parts = pathinfo($extra_info['path']);
8320
                $item_title = stripslashes($path_parts['filename']);
8321
            }
8322
        } elseif (is_numeric($extra_info)) {
8323
            $sql = "SELECT path, title FROM $tbl_doc
8324
                    WHERE
8325
                        c_id = ".$course_id." AND
8326
                        iid = ".intval($extra_info);
8327
            $result = Database::query($sql);
8328
            $row = Database::fetch_array($result);
8329
            $item_title = $row['title'];
8330
            $item_title = str_replace('_', ' ', $item_title);
8331
            if (empty($item_title)) {
8332
                $path_parts = pathinfo($row['path']);
8333
                $item_title = stripslashes($path_parts['filename']);
8334
            }
8335
        }
8336
8337
        $return = '<legend>';
8338
        $parent = 0;
8339
        if ($id != 0 && is_array($extra_info)) {
8340
            $parent = $extra_info['parent_item_id'];
8341
        }
8342
8343
        $arrLP = $this->getItemsForForm();
8344
        $this->tree_array($arrLP);
8345
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8346
        unset($this->arrMenu);
8347
8348
        if ($action == 'add') {
8349
            $return .= get_lang('CreateTheDocument');
8350
        } elseif ($action == 'move') {
8351
            $return .= get_lang('MoveTheCurrentDocument');
8352
        } else {
8353
            $return .= get_lang('EditTheCurrentDocument');
8354
        }
8355
        $return .= '</legend>';
8356
8357
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8358
            $return .= Display::return_message(
8359
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8360
                false
8361
            );
8362
        }
8363
        $form = new FormValidator(
8364
            'form',
8365
            'POST',
8366
            $this->getCurrentBuildingModeURL(),
8367
            '',
8368
            ['enctype' => 'multipart/form-data']
8369
        );
8370
        $defaults['title'] = Security::remove_XSS($item_title);
8371
        if (empty($item_title)) {
8372
            $defaults['title'] = Security::remove_XSS($item_title);
8373
        }
8374
        $defaults['description'] = $item_description;
8375
        $form->addElement('html', $return);
8376
8377
        if ($action != 'move') {
8378
            $data = $this->generate_lp_folder($_course);
8379
            if ($action != 'edit') {
8380
                $folders = DocumentManager::get_all_document_folders(
8381
                    $_course,
8382
                    0,
8383
                    true
8384
                );
8385
                DocumentManager::build_directory_selector(
8386
                    $folders,
8387
                    '',
8388
                    [],
8389
                    true,
8390
                    $form,
8391
                    'directory_parent_id'
8392
                );
8393
            }
8394
8395
            if (isset($data['id'])) {
8396
                $defaults['directory_parent_id'] = $data['id'];
8397
            }
8398
            $this->setItemTitle($form);
8399
        }
8400
8401
        $arrHide[0]['value'] = $this->name;
8402
        $arrHide[0]['padding'] = 20;
8403
8404
        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...
8405
            if ($action != 'add') {
8406
                if ($arrLP[$i]['item_type'] == 'dir' &&
8407
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8408
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8409
                ) {
8410
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8411
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8412
                }
8413
            } else {
8414
                if ($arrLP[$i]['item_type'] == 'dir') {
8415
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8416
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8417
                }
8418
            }
8419
        }
8420
8421
        $parentSelect = $form->addSelect(
8422
            'parent',
8423
            get_lang('Parent'),
8424
            [],
8425
            [
8426
                'id' => 'idParent',
8427
                'onchange' => 'javascript: load_cbo(this.value);',
8428
            ]
8429
        );
8430
8431
        $my_count = 0;
8432
        foreach ($arrHide as $key => $value) {
8433
            if ($my_count != 0) {
8434
                // The LP name is also the first section and is not in the same charset like the other sections.
8435
                $value['value'] = Security::remove_XSS($value['value']);
8436
                $parentSelect->addOption(
8437
                    $value['value'],
8438
                    $key,
8439
                    'style="padding-left:'.$value['padding'].'px;"'
8440
                );
8441
            } else {
8442
                $value['value'] = Security::remove_XSS($value['value']);
8443
                $parentSelect->addOption(
8444
                    $value['value'],
8445
                    $key,
8446
                    'style="padding-left:'.$value['padding'].'px;"'
8447
                );
8448
            }
8449
            $my_count++;
8450
        }
8451
8452
        if (!empty($id)) {
8453
            $parentSelect->setSelected($parent);
8454
        } else {
8455
            $parent_item_id = Session::read('parent_item_id', 0);
8456
            $parentSelect->setSelected($parent_item_id);
8457
        }
8458
8459
        if (is_array($arrLP)) {
8460
            reset($arrLP);
8461
        }
8462
8463
        $arrHide = [];
8464
        // POSITION
8465
        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...
8466
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8467
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8468
            ) {
8469
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8470
            }
8471
        }
8472
8473
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8474
8475
        $position = $form->addSelect(
8476
            'previous',
8477
            get_lang('Position'),
8478
            [],
8479
            ['id' => 'previous']
8480
        );
8481
8482
        $position->addOption(get_lang('FirstPosition'), 0);
8483
        foreach ($arrHide as $key => $value) {
8484
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8485
            $position->addOption(
8486
                $value['value'],
8487
                $key,
8488
                'style="padding-left:'.$padding.'px;"'
8489
            );
8490
        }
8491
8492
        $position->setSelected($selectedPosition);
8493
8494
        if (is_array($arrLP)) {
8495
            reset($arrLP);
8496
        }
8497
8498
        if ($action != 'move') {
8499
            $arrHide = [];
8500
            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...
8501
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8502
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8503
                ) {
8504
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8505
                }
8506
            }
8507
8508
            if (!$no_display_add) {
8509
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8510
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8511
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8512
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8513
                ) {
8514
                    if (isset($_POST['content'])) {
8515
                        $content = stripslashes($_POST['content']);
8516
                    } elseif (is_array($extra_info)) {
8517
                        //If it's an html document or a text file
8518
                        if (!$no_display_edit_textarea) {
8519
                            $content = $this->display_document(
8520
                                $extra_info['path'],
8521
                                false,
8522
                                false
8523
                            );
8524
                        }
8525
                    } elseif (is_numeric($extra_info)) {
8526
                        $content = $this->display_document(
8527
                            $extra_info,
8528
                            false,
8529
                            false
8530
                        );
8531
                    } else {
8532
                        $content = '';
8533
                    }
8534
8535
                    if (!$no_display_edit_textarea) {
8536
                        // We need to calculate here some specific settings for the online editor.
8537
                        // The calculated settings work for documents in the Documents tool
8538
                        // (on the root or in subfolders).
8539
                        // For documents in native scorm packages it is unclear whether the
8540
                        // online editor should be activated or not.
8541
8542
                        // A new document, it is in the root of the repository.
8543
                        $relative_path = '';
8544
                        $relative_prefix = '';
8545
                        if (is_array($extra_info) && $extra_info != 'new') {
8546
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8547
                            $relative_path = explode('/', $extra_info['dir']);
8548
                            $cnt = count($relative_path) - 2;
8549
                            if ($cnt < 0) {
8550
                                $cnt = 0;
8551
                            }
8552
                            $relative_prefix = str_repeat('../', $cnt);
8553
                            $relative_path = array_slice($relative_path, 1, $cnt);
8554
                            $relative_path = implode('/', $relative_path);
8555
                            if (strlen($relative_path) > 0) {
8556
                                $relative_path = $relative_path.'/';
8557
                            }
8558
                        } else {
8559
                            $result = $this->generate_lp_folder($_course);
8560
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8561
                            $relative_prefix = '../../';
8562
                        }
8563
8564
                        $editor_config = [
8565
                            'ToolbarSet' => 'LearningPathDocuments',
8566
                            'Width' => '100%',
8567
                            'Height' => '500',
8568
                            'FullPage' => true,
8569
                            'CreateDocumentDir' => $relative_prefix,
8570
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8571
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8572
                        ];
8573
8574
                        if ($_GET['action'] == 'add_item') {
8575
                            $class = 'add';
8576
                            $text = get_lang('LPCreateDocument');
8577
                        } else {
8578
                            if ($_GET['action'] == 'edit_item') {
8579
                                $class = 'save';
8580
                                $text = get_lang('SaveDocument');
8581
                            }
8582
                        }
8583
8584
                        $form->addButtonSave($text, 'submit_button');
8585
                        $renderer = $form->defaultRenderer();
8586
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8587
                        $form->addElement('html', '<div class="editor-lp">');
8588
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8589
                        $form->addElement('html', '</div>');
8590
                        $defaults['content_lp'] = $content;
8591
                    }
8592
                } elseif (is_numeric($extra_info)) {
8593
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8594
8595
                    $return = $this->display_document($extra_info, true, true, true);
8596
                    $form->addElement('html', $return);
8597
                }
8598
            }
8599
        }
8600
        if (isset($extra_info['item_type']) &&
8601
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8602
        ) {
8603
            $parentSelect->freeze();
8604
            $position->freeze();
8605
        }
8606
8607
        if ($action == 'move') {
8608
            $form->addElement('hidden', 'title', $item_title);
8609
            $form->addElement('hidden', 'description', $item_description);
8610
        }
8611
        if (is_numeric($extra_info)) {
8612
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8613
            $form->addElement('hidden', 'path', $extra_info);
8614
        } elseif (is_array($extra_info)) {
8615
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8616
            $form->addElement('hidden', 'path', $extra_info['path']);
8617
        }
8618
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8619
        $form->addElement('hidden', 'post_time', time());
8620
        $form->setDefaults($defaults);
8621
8622
        return $form->returnForm();
8623
    }
8624
8625
    /**
8626
     * Returns the form to update or create a read-out text.
8627
     *
8628
     * @param string $action     "add" or "edit"
8629
     * @param int    $id         ID of the lp_item (if already exists)
8630
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8631
     *
8632
     * @throws Exception
8633
     * @throws HTML_QuickForm_Error
8634
     *
8635
     * @return string HTML form
8636
     */
8637
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8638
    {
8639
        $course_id = api_get_course_int_id();
8640
        $_course = api_get_course_info();
8641
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8642
8643
        $no_display_edit_textarea = false;
8644
        $item_description = '';
8645
        //If action==edit document
8646
        //We don't display the document form if it's not an editable document (html or txt file)
8647
        if ($action == 'edit') {
8648
            if (is_array($extra_info)) {
8649
                $path_parts = pathinfo($extra_info['dir']);
8650
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8651
                    $no_display_edit_textarea = true;
8652
                }
8653
            }
8654
        }
8655
        $no_display_add = false;
8656
8657
        $item_title = '';
8658
        $item_description = '';
8659
        if ($id != 0 && is_array($extra_info)) {
8660
            $item_title = stripslashes($extra_info['title']);
8661
            $item_description = stripslashes($extra_info['description']);
8662
            $item_terms = stripslashes($extra_info['terms']);
8663
            if (empty($item_title)) {
8664
                $path_parts = pathinfo($extra_info['path']);
8665
                $item_title = stripslashes($path_parts['filename']);
8666
            }
8667
        } elseif (is_numeric($extra_info)) {
8668
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8669
            $result = Database::query($sql);
8670
            $row = Database::fetch_array($result);
8671
            $item_title = $row['title'];
8672
            $item_title = str_replace('_', ' ', $item_title);
8673
            if (empty($item_title)) {
8674
                $path_parts = pathinfo($row['path']);
8675
                $item_title = stripslashes($path_parts['filename']);
8676
            }
8677
        }
8678
8679
        $parent = 0;
8680
        if ($id != 0 && is_array($extra_info)) {
8681
            $parent = $extra_info['parent_item_id'];
8682
        }
8683
8684
        $arrLP = $this->getItemsForForm();
8685
        $this->tree_array($arrLP);
8686
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8687
        unset($this->arrMenu);
8688
8689
        if ($action === 'add') {
8690
            $formHeader = get_lang('CreateTheDocument');
8691
        } else {
8692
            $formHeader = get_lang('EditTheCurrentDocument');
8693
        }
8694
8695
        if ('edit' === $action) {
8696
            $urlAudioIcon = Display::url(
8697
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
8698
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8699
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8700
            );
8701
        } else {
8702
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
8703
        }
8704
8705
        $form = new FormValidator(
8706
            'frm_add_reading',
8707
            'POST',
8708
            $this->getCurrentBuildingModeURL(),
8709
            '',
8710
            ['enctype' => 'multipart/form-data']
8711
        );
8712
        $form->addHeader($formHeader);
8713
        $form->addHtml(
8714
            Display::return_message(
8715
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
8716
                'normal',
8717
                false
8718
            )
8719
        );
8720
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8721
        $defaults['description'] = $item_description;
8722
8723
        $data = $this->generate_lp_folder($_course);
8724
8725
        if ($action != 'edit') {
8726
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8727
            DocumentManager::build_directory_selector(
8728
                $folders,
8729
                '',
8730
                [],
8731
                true,
8732
                $form,
8733
                'directory_parent_id'
8734
            );
8735
        }
8736
8737
        if (isset($data['id'])) {
8738
            $defaults['directory_parent_id'] = $data['id'];
8739
        }
8740
        $this->setItemTitle($form);
8741
8742
        $arrHide[0]['value'] = $this->name;
8743
        $arrHide[0]['padding'] = 20;
8744
8745
        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...
8746
            if ($action != 'add') {
8747
                if ($arrLP[$i]['item_type'] == 'dir' &&
8748
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8749
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8750
                ) {
8751
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8752
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8753
                }
8754
            } else {
8755
                if ($arrLP[$i]['item_type'] == 'dir') {
8756
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8757
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8758
                }
8759
            }
8760
        }
8761
8762
        $parent_select = $form->addSelect(
8763
            'parent',
8764
            get_lang('Parent'),
8765
            [],
8766
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8767
        );
8768
8769
        $my_count = 0;
8770
        foreach ($arrHide as $key => $value) {
8771
            if ($my_count != 0) {
8772
                // The LP name is also the first section and is not in the same charset like the other sections.
8773
                $value['value'] = Security::remove_XSS($value['value']);
8774
                $parent_select->addOption(
8775
                    $value['value'],
8776
                    $key,
8777
                    'style="padding-left:'.$value['padding'].'px;"'
8778
                );
8779
            } else {
8780
                $value['value'] = Security::remove_XSS($value['value']);
8781
                $parent_select->addOption(
8782
                    $value['value'],
8783
                    $key,
8784
                    'style="padding-left:'.$value['padding'].'px;"'
8785
                );
8786
            }
8787
            $my_count++;
8788
        }
8789
8790
        if (!empty($id)) {
8791
            $parent_select->setSelected($parent);
8792
        } else {
8793
            $parent_item_id = Session::read('parent_item_id', 0);
8794
            $parent_select->setSelected($parent_item_id);
8795
        }
8796
8797
        if (is_array($arrLP)) {
8798
            reset($arrLP);
8799
        }
8800
8801
        $arrHide = [];
8802
        $s_selected_position = null;
8803
8804
        // POSITION
8805
        $lastPosition = null;
8806
8807
        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...
8808
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8809
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8810
            ) {
8811
                if ((isset($extra_info['previous_item_id']) &&
8812
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8813
                ) {
8814
                    $s_selected_position = $arrLP[$i]['id'];
8815
                }
8816
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8817
            }
8818
            $lastPosition = $arrLP[$i]['id'];
8819
        }
8820
8821
        if (empty($s_selected_position)) {
8822
            $s_selected_position = $lastPosition;
8823
        }
8824
8825
        $position = $form->addSelect(
8826
            'previous',
8827
            get_lang('Position'),
8828
            []
8829
        );
8830
        $position->addOption(get_lang('FirstPosition'), 0);
8831
8832
        foreach ($arrHide as $key => $value) {
8833
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8834
            $position->addOption(
8835
                $value['value'],
8836
                $key,
8837
                'style="padding-left:'.$padding.'px;"'
8838
            );
8839
        }
8840
        $position->setSelected($s_selected_position);
8841
8842
        if (is_array($arrLP)) {
8843
            reset($arrLP);
8844
        }
8845
8846
        $arrHide = [];
8847
8848
        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...
8849
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8850
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8851
            ) {
8852
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8853
            }
8854
        }
8855
8856
        if (!$no_display_add) {
8857
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8858
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8859
8860
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8861
                if (!$no_display_edit_textarea) {
8862
                    $content = '';
8863
8864
                    if (isset($_POST['content'])) {
8865
                        $content = stripslashes($_POST['content']);
8866
                    } elseif (is_array($extra_info)) {
8867
                        $content = $this->display_document($extra_info['path'], false, false);
8868
                    } elseif (is_numeric($extra_info)) {
8869
                        $content = $this->display_document($extra_info, false, false);
8870
                    }
8871
8872
                    // A new document, it is in the root of the repository.
8873
                    if (is_array($extra_info) && $extra_info != 'new') {
8874
                    } else {
8875
                        $this->generate_lp_folder($_course);
8876
                    }
8877
8878
                    if ($_GET['action'] == 'add_item') {
8879
                        $text = get_lang('LPCreateDocument');
8880
                    } else {
8881
                        $text = get_lang('SaveDocument');
8882
                    }
8883
8884
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8885
                    $form
8886
                        ->defaultRenderer()
8887
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8888
                    $form->addButtonSave($text, 'submit_button');
8889
                    $defaults['content_lp'] = $content;
8890
                }
8891
            } elseif (is_numeric($extra_info)) {
8892
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8893
8894
                $return = $this->display_document($extra_info, true, true, true);
8895
                $form->addElement('html', $return);
8896
            }
8897
        }
8898
8899
        if (is_numeric($extra_info)) {
8900
            $form->addElement('hidden', 'path', $extra_info);
8901
        } elseif (is_array($extra_info)) {
8902
            $form->addElement('hidden', 'path', $extra_info['path']);
8903
        }
8904
8905
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8906
        $form->addElement('hidden', 'post_time', time());
8907
        $form->setDefaults($defaults);
8908
8909
        return $form->returnForm();
8910
    }
8911
8912
    /**
8913
     * @param array  $courseInfo
8914
     * @param string $content
8915
     * @param string $title
8916
     * @param int    $parentId
8917
     *
8918
     * @throws \Doctrine\ORM\ORMException
8919
     * @throws \Doctrine\ORM\OptimisticLockException
8920
     * @throws \Doctrine\ORM\TransactionRequiredException
8921
     *
8922
     * @return int
8923
     */
8924
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8925
    {
8926
        $creatorId = api_get_user_id();
8927
        $sessionId = api_get_session_id();
8928
8929
        // Generates folder
8930
        $result = $this->generate_lp_folder($courseInfo);
8931
        $dir = $result['dir'];
8932
8933
        if (empty($parentId) || $parentId == '/') {
8934
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8935
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8936
8937
            if ($parentId === '/') {
8938
                $dir = '/';
8939
            }
8940
8941
            // Please, do not modify this dirname formatting.
8942
            if (strstr($dir, '..')) {
8943
                $dir = '/';
8944
            }
8945
8946
            if (!empty($dir[0]) && $dir[0] == '.') {
8947
                $dir = substr($dir, 1);
8948
            }
8949
            if (!empty($dir[0]) && $dir[0] != '/') {
8950
                $dir = '/'.$dir;
8951
            }
8952
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8953
                $dir .= '/';
8954
            }
8955
        } else {
8956
            $parentInfo = DocumentManager::get_document_data_by_id(
8957
                $parentId,
8958
                $courseInfo['code']
8959
            );
8960
            if (!empty($parentInfo)) {
8961
                $dir = $parentInfo['path'].'/';
8962
            }
8963
        }
8964
8965
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8966
8967
        if (!is_dir($filepath)) {
8968
            $dir = '/';
8969
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8970
        }
8971
8972
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8973
8974
        if (!empty($title)) {
8975
            $title = api_replace_dangerous_char(stripslashes($title));
8976
        } else {
8977
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8978
        }
8979
8980
        $title = disable_dangerous_file($title);
8981
        $filename = $title;
8982
        $content = !empty($content) ? $content : $_POST['content_lp'];
8983
        $tmpFileName = $filename;
8984
8985
        $i = 0;
8986
        while (file_exists($filepath.$tmpFileName.'.html')) {
8987
            $tmpFileName = $filename.'_'.++$i;
8988
        }
8989
8990
        $filename = $tmpFileName.'.html';
8991
        $content = stripslashes($content);
8992
8993
        if (file_exists($filepath.$filename)) {
8994
            return 0;
8995
        }
8996
8997
        $putContent = file_put_contents($filepath.$filename, $content);
8998
8999
        if ($putContent === false) {
9000
            return 0;
9001
        }
9002
9003
        $fileSize = filesize($filepath.$filename);
9004
        $saveFilePath = $dir.$filename;
9005
9006
        $document = DocumentManager::addDocument(
9007
            $courseInfo,
9008
            $saveFilePath,
9009
            'file',
9010
            $fileSize,
9011
            $tmpFileName,
9012
            '',
9013
            0, //readonly
9014
            true,
9015
            null,
9016
            $sessionId,
9017
            $creatorId
9018
        );
9019
9020
        $documentId = $document->getId();
9021
9022
        if (!$document) {
9023
            return 0;
9024
        }
9025
9026
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9027
        $newTitle = $originalTitle;
9028
9029
        if ($newComment || $newTitle) {
9030
            $em = Database::getManager();
9031
9032
            if ($newComment) {
9033
                $document->setComment($newComment);
9034
            }
9035
9036
            if ($newTitle) {
9037
                $document->setTitle($newTitle);
9038
            }
9039
9040
            $em->persist($document);
9041
            $em->flush();
9042
        }
9043
9044
        return $documentId;
9045
    }
9046
9047
    /**
9048
     * Return HTML form to add/edit a link item.
9049
     *
9050
     * @param string $action     (add/edit)
9051
     * @param int    $id         Item ID if exists
9052
     * @param mixed  $extra_info
9053
     *
9054
     * @throws Exception
9055
     * @throws HTML_QuickForm_Error
9056
     *
9057
     * @return string HTML form
9058
     */
9059
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9060
    {
9061
        $course_id = api_get_course_int_id();
9062
        $tbl_link = Database::get_course_table(TABLE_LINK);
9063
9064
        $item_title = '';
9065
        $item_description = '';
9066
        $item_url = '';
9067
9068
        if ($id != 0 && is_array($extra_info)) {
9069
            $item_title = stripslashes($extra_info['title']);
9070
            $item_description = stripslashes($extra_info['description']);
9071
            $item_url = stripslashes($extra_info['url']);
9072
        } elseif (is_numeric($extra_info)) {
9073
            $extra_info = (int) $extra_info;
9074
            $sql = "SELECT title, description, url 
9075
                    FROM $tbl_link
9076
                    WHERE c_id = $course_id AND iid = $extra_info";
9077
            $result = Database::query($sql);
9078
            $row = Database::fetch_array($result);
9079
            $item_title = $row['title'];
9080
            $item_description = $row['description'];
9081
            $item_url = $row['url'];
9082
        }
9083
9084
        $form = new FormValidator(
9085
            'edit_link',
9086
            'POST',
9087
            $this->getCurrentBuildingModeURL()
9088
        );
9089
        $defaults = [];
9090
        $parent = 0;
9091
        if ($id != 0 && is_array($extra_info)) {
9092
            $parent = $extra_info['parent_item_id'];
9093
        }
9094
9095
        $arrLP = $this->getItemsForForm();
9096
9097
        $this->tree_array($arrLP);
9098
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9099
        unset($this->arrMenu);
9100
9101
        if ($action == 'add') {
9102
            $legend = get_lang('CreateTheLink');
9103
        } elseif ($action == 'move') {
9104
            $legend = get_lang('MoveCurrentLink');
9105
        } else {
9106
            $legend = get_lang('EditCurrentLink');
9107
        }
9108
9109
        $form->addHeader($legend);
9110
9111
        if ($action != 'move') {
9112
            $this->setItemTitle($form);
9113
            $defaults['title'] = $item_title;
9114
        }
9115
9116
        $selectParent = $form->addSelect(
9117
            'parent',
9118
            get_lang('Parent'),
9119
            [],
9120
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9121
        );
9122
        $selectParent->addOption($this->name, 0);
9123
        $arrHide = [
9124
            $id,
9125
        ];
9126
9127
        $parent_item_id = Session::read('parent_item_id', 0);
9128
9129
        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...
9130
            if ($action != 'add') {
9131
                if (
9132
                    ($arrLP[$i]['item_type'] == 'dir') &&
9133
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9134
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9135
                ) {
9136
                    $selectParent->addOption(
9137
                        $arrLP[$i]['title'],
9138
                        $arrLP[$i]['id'],
9139
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9140
                    );
9141
9142
                    if ($parent == $arrLP[$i]['id']) {
9143
                        $selectParent->setSelected($arrLP[$i]['id']);
9144
                    }
9145
                } else {
9146
                    $arrHide[] = $arrLP[$i]['id'];
9147
                }
9148
            } else {
9149
                if ($arrLP[$i]['item_type'] == 'dir') {
9150
                    $selectParent->addOption(
9151
                        $arrLP[$i]['title'],
9152
                        $arrLP[$i]['id'],
9153
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9154
                    );
9155
9156
                    if ($parent_item_id == $arrLP[$i]['id']) {
9157
                        $selectParent->setSelected($arrLP[$i]['id']);
9158
                    }
9159
                }
9160
            }
9161
        }
9162
9163
        if (is_array($arrLP)) {
9164
            reset($arrLP);
9165
        }
9166
9167
        $selectPrevious = $form->addSelect(
9168
            'previous',
9169
            get_lang('Position'),
9170
            [],
9171
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9172
        );
9173
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9174
9175
        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...
9176
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9177
                $selectPrevious->addOption(
9178
                    $arrLP[$i]['title'],
9179
                    $arrLP[$i]['id']
9180
                );
9181
9182
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9183
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9184
                } elseif ($action == 'add') {
9185
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9186
                }
9187
            }
9188
        }
9189
9190
        if ($action != 'move') {
9191
            $urlAttributes = ['class' => 'learnpath_item_form'];
9192
9193
            if (is_numeric($extra_info)) {
9194
                $urlAttributes['disabled'] = 'disabled';
9195
            }
9196
9197
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9198
            $defaults['url'] = $item_url;
9199
            $arrHide = [];
9200
            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...
9201
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9202
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9203
                }
9204
            }
9205
        }
9206
9207
        if ($action == 'add') {
9208
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9209
        } else {
9210
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9211
        }
9212
9213
        if ($action == 'move') {
9214
            $form->addHidden('title', $item_title);
9215
            $form->addHidden('description', $item_description);
9216
        }
9217
9218
        if (is_numeric($extra_info)) {
9219
            $form->addHidden('path', $extra_info);
9220
        } elseif (is_array($extra_info)) {
9221
            $form->addHidden('path', $extra_info['path']);
9222
        }
9223
        $form->addHidden('type', TOOL_LINK);
9224
        $form->addHidden('post_time', time());
9225
        $form->setDefaults($defaults);
9226
9227
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9228
    }
9229
9230
    /**
9231
     * Return HTML form to add/edit a student publication (work).
9232
     *
9233
     * @param string $action
9234
     * @param int    $id         Item ID if already exists
9235
     * @param string $extra_info
9236
     *
9237
     * @throws Exception
9238
     *
9239
     * @return string HTML form
9240
     */
9241
    public function display_student_publication_form(
9242
        $action = 'add',
9243
        $id = 0,
9244
        $extra_info = ''
9245
    ) {
9246
        $course_id = api_get_course_int_id();
9247
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9248
9249
        $item_title = get_lang('Student_publication');
9250
        if ($id != 0 && is_array($extra_info)) {
9251
            $item_title = stripslashes($extra_info['title']);
9252
            $item_description = stripslashes($extra_info['description']);
9253
        } elseif (is_numeric($extra_info)) {
9254
            $extra_info = (int) $extra_info;
9255
            $sql = "SELECT title, description
9256
                    FROM $tbl_publication
9257
                    WHERE c_id = $course_id AND id = ".$extra_info;
9258
9259
            $result = Database::query($sql);
9260
            $row = Database::fetch_array($result);
9261
            if ($row) {
9262
                $item_title = $row['title'];
9263
            }
9264
        }
9265
9266
        $parent = 0;
9267
        if ($id != 0 && is_array($extra_info)) {
9268
            $parent = $extra_info['parent_item_id'];
9269
        }
9270
9271
        $arrLP = $this->getItemsForForm();
9272
9273
        $this->tree_array($arrLP);
9274
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9275
        unset($this->arrMenu);
9276
9277
        $form = new FormValidator('frm_student_publication', 'post', '#');
9278
9279
        if ($action == 'add') {
9280
            $form->addHeader(get_lang('Student_publication'));
9281
        } elseif ($action == 'move') {
9282
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9283
        } else {
9284
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9285
        }
9286
9287
        if ($action != 'move') {
9288
            $this->setItemTitle($form);
9289
        }
9290
9291
        $parentSelect = $form->addSelect(
9292
            'parent',
9293
            get_lang('Parent'),
9294
            ['0' => $this->name],
9295
            [
9296
                'onchange' => 'javascript: load_cbo(this.value);',
9297
                'class' => 'learnpath_item_form',
9298
                'id' => 'idParent',
9299
            ]
9300
        );
9301
9302
        $arrHide = [$id];
9303
        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...
9304
            if ($action != 'add') {
9305
                if (
9306
                    ($arrLP[$i]['item_type'] == 'dir') &&
9307
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9308
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9309
                ) {
9310
                    $parentSelect->addOption(
9311
                        $arrLP[$i]['title'],
9312
                        $arrLP[$i]['id'],
9313
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9314
                    );
9315
9316
                    if ($parent == $arrLP[$i]['id']) {
9317
                        $parentSelect->setSelected($arrLP[$i]['id']);
9318
                    }
9319
                } else {
9320
                    $arrHide[] = $arrLP[$i]['id'];
9321
                }
9322
            } else {
9323
                if ($arrLP[$i]['item_type'] == 'dir') {
9324
                    $parentSelect->addOption(
9325
                        $arrLP[$i]['title'],
9326
                        $arrLP[$i]['id'],
9327
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9328
                    );
9329
9330
                    if ($parent == $arrLP[$i]['id']) {
9331
                        $parentSelect->setSelected($arrLP[$i]['id']);
9332
                    }
9333
                }
9334
            }
9335
        }
9336
9337
        if (is_array($arrLP)) {
9338
            reset($arrLP);
9339
        }
9340
9341
        $previousSelect = $form->addSelect(
9342
            'previous',
9343
            get_lang('Position'),
9344
            ['0' => get_lang('FirstPosition')],
9345
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9346
        );
9347
9348
        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...
9349
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9350
                $previousSelect->addOption(
9351
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9352
                    $arrLP[$i]['id']
9353
                );
9354
9355
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9356
                    $previousSelect->setSelected($arrLP[$i]['id']);
9357
                } elseif ($action == 'add') {
9358
                    $previousSelect->setSelected($arrLP[$i]['id']);
9359
                }
9360
            }
9361
        }
9362
9363
        if ($action == 'add') {
9364
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9365
        } else {
9366
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9367
        }
9368
9369
        if ($action == 'move') {
9370
            $form->addHidden('title', $item_title);
9371
            $form->addHidden('description', $item_description);
9372
        }
9373
9374
        if (is_numeric($extra_info)) {
9375
            $form->addHidden('path', $extra_info);
9376
        } elseif (is_array($extra_info)) {
9377
            $form->addHidden('path', $extra_info['path']);
9378
        }
9379
9380
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9381
        $form->addHidden('post_time', time());
9382
        $form->setDefaults(['title' => $item_title]);
9383
9384
        $return = '<div class="sectioncomment">';
9385
        $return .= $form->returnForm();
9386
        $return .= '</div>';
9387
9388
        return $return;
9389
    }
9390
9391
    /**
9392
     * Displays the menu for manipulating a step.
9393
     *
9394
     * @param id     $item_id
9395
     * @param string $item_type
9396
     *
9397
     * @return string
9398
     */
9399
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9400
    {
9401
        $_course = api_get_course_info();
9402
        $course_code = api_get_course_id();
9403
        $item_id = (int) $item_id;
9404
9405
        $return = '<div class="actions">';
9406
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9407
        $sql = "SELECT * FROM $tbl_lp_item 
9408
                WHERE iid = ".$item_id;
9409
        $result = Database::query($sql);
9410
        $row = Database::fetch_assoc($result);
9411
9412
        $audio_player = null;
9413
        // We display an audio player if needed.
9414
        if (!empty($row['audio'])) {
9415
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9416
9417
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9418
                .'<audio src="'.$webAudioPath.'" controls>'
9419
                .'</div><br>';
9420
        }
9421
9422
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9423
9424
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9425
            $return .= Display::url(
9426
                Display::return_icon(
9427
                    'edit.png',
9428
                    get_lang('Edit'),
9429
                    [],
9430
                    ICON_SIZE_SMALL
9431
                ),
9432
                $url.'&action=edit_item&path_item='.$row['path']
9433
            );
9434
9435
            $return .= Display::url(
9436
                Display::return_icon(
9437
                    'move.png',
9438
                    get_lang('Move'),
9439
                    [],
9440
                    ICON_SIZE_SMALL
9441
                ),
9442
                $url.'&action=move_item'
9443
            );
9444
        }
9445
9446
        // Commented for now as prerequisites cannot be added to chapters.
9447
        if ($item_type != 'dir') {
9448
            $return .= Display::url(
9449
                Display::return_icon(
9450
                    'accept.png',
9451
                    get_lang('LearnpathPrerequisites'),
9452
                    [],
9453
                    ICON_SIZE_SMALL
9454
                ),
9455
                $url.'&action=edit_item_prereq'
9456
            );
9457
        }
9458
        $return .= Display::url(
9459
            Display::return_icon(
9460
                'delete.png',
9461
                get_lang('Delete'),
9462
                [],
9463
                ICON_SIZE_SMALL
9464
            ),
9465
            $url.'&action=delete_item'
9466
        );
9467
9468
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9469
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9470
            if (empty($documentData)) {
9471
                // Try with iid
9472
                $table = Database::get_course_table(TABLE_DOCUMENT);
9473
                $sql = "SELECT path FROM $table
9474
                        WHERE 
9475
                              c_id = ".api_get_course_int_id()." AND 
9476
                              iid = ".$row['path']." AND 
9477
                              path NOT LIKE '%_DELETED_%'";
9478
                $result = Database::query($sql);
9479
                $documentData = Database::fetch_array($result);
9480
                if ($documentData) {
9481
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9482
                }
9483
            }
9484
            if (isset($documentData['absolute_path_from_document'])) {
9485
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9486
            }
9487
        }
9488
9489
        $return .= '</div>';
9490
9491
        if (!empty($audio_player)) {
9492
            $return .= $audio_player;
9493
        }
9494
9495
        return $return;
9496
    }
9497
9498
    /**
9499
     * Creates the javascript needed for filling up the checkboxes without page reload.
9500
     *
9501
     * @return string
9502
     */
9503
    public function get_js_dropdown_array()
9504
    {
9505
        $course_id = api_get_course_int_id();
9506
        $return = 'var child_name = new Array();'."\n";
9507
        $return .= 'var child_value = new Array();'."\n\n";
9508
        $return .= 'child_name[0] = new Array();'."\n";
9509
        $return .= 'child_value[0] = new Array();'."\n\n";
9510
9511
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9512
        $sql = "SELECT * FROM ".$tbl_lp_item."
9513
                WHERE 
9514
                    c_id = $course_id AND 
9515
                    lp_id = ".$this->lp_id." AND 
9516
                    parent_item_id = 0
9517
                ORDER BY display_order ASC";
9518
        $res_zero = Database::query($sql);
9519
        $i = 0;
9520
9521
        $list = $this->getItemsForForm(true);
9522
9523
        foreach ($list as $row_zero) {
9524
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9525
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9526
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9527
                }
9528
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9529
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9530
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9531
            }
9532
        }
9533
9534
        $return .= "\n";
9535
        $sql = "SELECT * FROM $tbl_lp_item
9536
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9537
        $res = Database::query($sql);
9538
        while ($row = Database::fetch_array($res)) {
9539
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9540
                           WHERE
9541
                                c_id = ".$course_id." AND
9542
                                parent_item_id = ".$row['iid']."
9543
                           ORDER BY display_order ASC";
9544
            $res_parent = Database::query($sql_parent);
9545
            $i = 0;
9546
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9547
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9548
9549
            while ($row_parent = Database::fetch_array($res_parent)) {
9550
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9551
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9552
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9553
            }
9554
            $return .= "\n";
9555
        }
9556
9557
        $return .= "
9558
            function load_cbo(id) {
9559
                if (!id) {
9560
                    return false;
9561
                }
9562
            
9563
                var cbo = document.getElementById('previous');
9564
                for(var i = cbo.length - 1; i > 0; i--) {
9565
                    cbo.options[i] = null;
9566
                }
9567
            
9568
                var k=0;
9569
                for(var i = 1; i <= child_name[id].length; i++){
9570
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9571
                    option.style.paddingLeft = '40px';
9572
                    cbo.options[i] = option;
9573
                    k = i;
9574
                }
9575
            
9576
                cbo.options[k].selected = true;
9577
                $('#previous').selectpicker('refresh');
9578
            }";
9579
9580
        return $return;
9581
    }
9582
9583
    /**
9584
     * Display the form to allow moving an item.
9585
     *
9586
     * @param int $item_id Item ID
9587
     *
9588
     * @throws Exception
9589
     * @throws HTML_QuickForm_Error
9590
     *
9591
     * @return string HTML form
9592
     */
9593
    public function display_move_item($item_id)
9594
    {
9595
        $return = '';
9596
        if (is_numeric($item_id)) {
9597
            $item_id = (int) $item_id;
9598
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9599
9600
            $sql = "SELECT * FROM $tbl_lp_item
9601
                    WHERE iid = $item_id";
9602
            $res = Database::query($sql);
9603
            $row = Database::fetch_array($res);
9604
9605
            switch ($row['item_type']) {
9606
                case 'dir':
9607
                case 'asset':
9608
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9609
                    $return .= $this->display_item_form(
9610
                        $row['item_type'],
9611
                        get_lang('MoveCurrentChapter'),
9612
                        'move',
9613
                        $item_id,
9614
                        $row
9615
                    );
9616
                    break;
9617
                case TOOL_DOCUMENT:
9618
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9619
                    $return .= $this->display_document_form('move', $item_id, $row);
9620
                    break;
9621
                case TOOL_LINK:
9622
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9623
                    $return .= $this->display_link_form('move', $item_id, $row);
9624
                    break;
9625
                case TOOL_HOTPOTATOES:
9626
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9627
                    $return .= $this->display_link_form('move', $item_id, $row);
9628
                    break;
9629
                case TOOL_QUIZ:
9630
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9631
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9632
                    break;
9633
                case TOOL_STUDENTPUBLICATION:
9634
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9635
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9636
                    break;
9637
                case TOOL_FORUM:
9638
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9639
                    $return .= $this->display_forum_form('move', $item_id, $row);
9640
                    break;
9641
                case TOOL_THREAD:
9642
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9643
                    $return .= $this->display_forum_form('move', $item_id, $row);
9644
                    break;
9645
            }
9646
        }
9647
9648
        return $return;
9649
    }
9650
9651
    /**
9652
     * Return HTML form to allow prerequisites selection.
9653
     *
9654
     * @todo use FormValidator
9655
     *
9656
     * @param int Item ID
9657
     *
9658
     * @return string HTML form
9659
     */
9660
    public function display_item_prerequisites_form($item_id = 0)
9661
    {
9662
        $course_id = api_get_course_int_id();
9663
        $item_id = (int) $item_id;
9664
9665
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9666
9667
        /* Current prerequisite */
9668
        $sql = "SELECT * FROM $tbl_lp_item
9669
                WHERE iid = $item_id";
9670
        $result = Database::query($sql);
9671
        $row = Database::fetch_array($result);
9672
        $prerequisiteId = $row['prerequisite'];
9673
        $return = '<legend>';
9674
        $return .= get_lang('AddEditPrerequisites');
9675
        $return .= '</legend>';
9676
        $return .= '<form method="POST">';
9677
        $return .= '<div class="table-responsive">';
9678
        $return .= '<table class="table table-hover">';
9679
        $return .= '<thead>';
9680
        $return .= '<tr>';
9681
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9682
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9683
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9684
        $return .= '</tr>';
9685
        $return .= '</thead>';
9686
9687
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9688
        $return .= '<tbody>';
9689
        $return .= '<tr>';
9690
        $return .= '<td colspan="3">';
9691
        $return .= '<div class="radio learnpath"><label for="idNone">';
9692
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9693
        $return .= get_lang('None').'</label>';
9694
        $return .= '</div>';
9695
        $return .= '</tr>';
9696
9697
        $sql = "SELECT * FROM $tbl_lp_item
9698
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9699
        $result = Database::query($sql);
9700
9701
        $selectedMinScore = [];
9702
        $selectedMaxScore = [];
9703
        $masteryScore = [];
9704
        while ($row = Database::fetch_array($result)) {
9705
            if ($row['iid'] == $item_id) {
9706
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9707
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9708
            }
9709
            $masteryScore[$row['iid']] = $row['mastery_score'];
9710
        }
9711
9712
        $arrLP = $this->getItemsForForm();
9713
        $this->tree_array($arrLP);
9714
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9715
        unset($this->arrMenu);
9716
9717
        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...
9718
            $item = $arrLP[$i];
9719
9720
            if ($item['id'] == $item_id) {
9721
                break;
9722
            }
9723
9724
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9725
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9726
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9727
9728
            $return .= '<tr>';
9729
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9730
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9731
            $return .= '<label for="id'.$item['id'].'">';
9732
            $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'].'" />';
9733
9734
            $icon_name = str_replace(' ', '', $item['item_type']);
9735
9736
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9737
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9738
            } else {
9739
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9740
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9741
                } else {
9742
                    $return .= Display::return_icon('folder_document.png');
9743
                }
9744
            }
9745
9746
            $return .= $item['title'].'</label>';
9747
            $return .= '</div>';
9748
            $return .= '</td>';
9749
9750
            if ($item['item_type'] == TOOL_QUIZ) {
9751
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9752
                $lpItemObj = new LpItem($course_id, $item['id']);
9753
                $exercise = new Exercise($course_id);
9754
                $exercise->read($lpItemObj->path);
9755
                $lpItemObj->max_score = $exercise->get_max_score();
9756
                $lpItemObj->update();
9757
                $item['max_score'] = $lpItemObj->max_score;
9758
9759
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9760
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9761
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9762
                }
9763
9764
                $return .= '<td>';
9765
                $return .= '<input 
9766
                    class="form-control" 
9767
                    size="4" maxlength="3" 
9768
                    name="min_'.$item['id'].'" 
9769
                    type="number" 
9770
                    min="0" 
9771
                    step="1" 
9772
                    max="'.$item['max_score'].'" 
9773
                    value="'.$selectedMinScoreValue.'" 
9774
                />';
9775
                $return .= '</td>';
9776
                $return .= '<td>';
9777
                $return .= '<input 
9778
                    class="form-control" 
9779
                    size="4" 
9780
                    maxlength="3" 
9781
                    name="max_'.$item['id'].'" 
9782
                    type="number" 
9783
                    min="0" 
9784
                    step="1" 
9785
                    max="'.$item['max_score'].'" 
9786
                    value="'.$selectedMaxScoreValue.'" 
9787
                />';
9788
                $return .= '</td>';
9789
            }
9790
9791
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9792
                $return .= '<td>';
9793
                $return .= '<input 
9794
                    size="4" 
9795
                    maxlength="3" 
9796
                    name="min_'.$item['id'].'" 
9797
                    type="number" 
9798
                    min="0" 
9799
                    step="1" 
9800
                    max="'.$item['max_score'].'" 
9801
                    value="'.$selectedMinScoreValue.'" 
9802
                />';
9803
                $return .= '</td>';
9804
                $return .= '<td>';
9805
                $return .= '<input 
9806
                    size="4" 
9807
                    maxlength="3" 
9808
                    name="max_'.$item['id'].'" 
9809
                    type="number" 
9810
                    min="0" 
9811
                    step="1" 
9812
                    max="'.$item['max_score'].'" 
9813
                    value="'.$selectedMaxScoreValue.'" 
9814
                />';
9815
                $return .= '</td>';
9816
            }
9817
            $return .= '</tr>';
9818
        }
9819
        $return .= '<tr>';
9820
        $return .= '</tr>';
9821
        $return .= '</tbody>';
9822
        $return .= '</table>';
9823
        $return .= '</div>';
9824
        $return .= '<div class="form-group">';
9825
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9826
            get_lang('ModifyPrerequisites').'</button>';
9827
        $return .= '</form>';
9828
9829
        return $return;
9830
    }
9831
9832
    /**
9833
     * Return HTML list to allow prerequisites selection for lp.
9834
     *
9835
     * @return string HTML form
9836
     */
9837
    public function display_lp_prerequisites_list()
9838
    {
9839
        $course_id = api_get_course_int_id();
9840
        $lp_id = $this->lp_id;
9841
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9842
9843
        // get current prerequisite
9844
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9845
        $result = Database::query($sql);
9846
        $row = Database::fetch_array($result);
9847
        $prerequisiteId = $row['prerequisite'];
9848
        $session_id = api_get_session_id();
9849
        $session_condition = api_get_session_condition($session_id, true, true);
9850
        $sql = "SELECT * FROM $tbl_lp
9851
                WHERE c_id = $course_id $session_condition
9852
                ORDER BY display_order ";
9853
        $rs = Database::query($sql);
9854
        $return = '';
9855
        $return .= '<select name="prerequisites" class="form-control">';
9856
        $return .= '<option value="0">'.get_lang('None').'</option>';
9857
        if (Database::num_rows($rs) > 0) {
9858
            while ($row = Database::fetch_array($rs)) {
9859
                if ($row['id'] == $lp_id) {
9860
                    continue;
9861
                }
9862
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9863
            }
9864
        }
9865
        $return .= '</select>';
9866
9867
        return $return;
9868
    }
9869
9870
    /**
9871
     * Creates a list with all the documents in it.
9872
     *
9873
     * @param bool $showInvisibleFiles
9874
     *
9875
     * @throws Exception
9876
     * @throws HTML_QuickForm_Error
9877
     *
9878
     * @return string
9879
     */
9880
    public function get_documents($showInvisibleFiles = false)
9881
    {
9882
        $course_info = api_get_course_info();
9883
        $sessionId = api_get_session_id();
9884
        $documentTree = DocumentManager::get_document_preview(
9885
            $course_info,
9886
            $this->lp_id,
9887
            null,
9888
            $sessionId,
9889
            true,
9890
            null,
9891
            null,
9892
            $showInvisibleFiles,
9893
            true
9894
        );
9895
9896
        $headers = [
9897
            get_lang('Files'),
9898
            get_lang('CreateTheDocument'),
9899
            get_lang('CreateReadOutText'),
9900
            get_lang('Upload'),
9901
        ];
9902
9903
        $form = new FormValidator(
9904
            'form_upload',
9905
            'POST',
9906
            $this->getCurrentBuildingModeURL(),
9907
            '',
9908
            ['enctype' => 'multipart/form-data']
9909
        );
9910
9911
        $folders = DocumentManager::get_all_document_folders(
9912
            api_get_course_info(),
9913
            0,
9914
            true
9915
        );
9916
9917
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9918
9919
        DocumentManager::build_directory_selector(
9920
            $folders,
9921
            $lpPathInfo['id'],
9922
            [],
9923
            true,
9924
            $form,
9925
            'directory_parent_id'
9926
        );
9927
9928
        $group = [
9929
            $form->createElement(
9930
                'radio',
9931
                'if_exists',
9932
                get_lang('UplWhatIfFileExists'),
9933
                get_lang('UplDoNothing'),
9934
                'nothing'
9935
            ),
9936
            $form->createElement(
9937
                'radio',
9938
                'if_exists',
9939
                null,
9940
                get_lang('UplOverwriteLong'),
9941
                'overwrite'
9942
            ),
9943
            $form->createElement(
9944
                'radio',
9945
                'if_exists',
9946
                null,
9947
                get_lang('UplRenameLong'),
9948
                'rename'
9949
            ),
9950
        ];
9951
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
9952
9953
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9954
        $defaultFileExistsOption = 'rename';
9955
        if (!empty($fileExistsOption)) {
9956
            $defaultFileExistsOption = $fileExistsOption;
9957
        }
9958
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9959
9960
        // Check box options
9961
        $form->addElement(
9962
            'checkbox',
9963
            'unzip',
9964
            get_lang('Options'),
9965
            get_lang('Uncompress')
9966
        );
9967
9968
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9969
        $form->addMultipleUpload($url);
9970
        $new = $this->display_document_form('add', 0);
9971
        $frmReadOutText = $this->displayFrmReadOutText('add');
9972
        $tabs = Display::tabs(
9973
            $headers,
9974
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9975
            'subtab'
9976
        );
9977
9978
        return $tabs;
9979
    }
9980
9981
    /**
9982
     * Creates a list with all the exercises (quiz) in it.
9983
     *
9984
     * @return string
9985
     */
9986
    public function get_exercises()
9987
    {
9988
        $course_id = api_get_course_int_id();
9989
        $session_id = api_get_session_id();
9990
        $userInfo = api_get_user_info();
9991
9992
        // New for hotpotatoes.
9993
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
9994
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9995
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9996
        $condition_session = api_get_session_condition($session_id, true, true);
9997
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
9998
9999
        $activeCondition = ' active <> -1 ';
10000
        if ($setting) {
10001
            $activeCondition = ' active = 1 ';
10002
        }
10003
10004
        $categoryCondition = '';
10005
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10006
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10007
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10008
        }
10009
10010
        $keywordCondition = '';
10011
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10012
10013
        if (!empty($keyword)) {
10014
            $keyword = Database::escape_string($keyword);
10015
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10016
        }
10017
10018
        $sql_quiz = "SELECT * FROM $tbl_quiz
10019
                     WHERE 
10020
                            c_id = $course_id AND 
10021
                            $activeCondition
10022
                            $condition_session 
10023
                            $categoryCondition
10024
                            $keywordCondition
10025
                     ORDER BY title ASC";
10026
10027
        $sql_hot = "SELECT * FROM $tbl_doc
10028
                    WHERE 
10029
                        c_id = $course_id AND 
10030
                        path LIKE '".$uploadPath."/%/%htm%'  
10031
                        $condition_session
10032
                     ORDER BY id ASC";
10033
10034
        $res_quiz = Database::query($sql_quiz);
10035
        $res_hot = Database::query($sql_hot);
10036
10037
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10038
10039
        // Create a search-box
10040
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10041
        $form->addHidden('action', 'add_item');
10042
        $form->addHidden('type', 'step');
10043
        $form->addHidden('lp_id', $this->lp_id);
10044
        $form->addHidden('lp_build_selected', '2');
10045
10046
        $form->addCourseHiddenParams();
10047
        $form->addText(
10048
            'keyword',
10049
            get_lang('Search'),
10050
            false,
10051
            [
10052
                'aria-label' => get_lang('Search'),
10053
            ]
10054
        );
10055
10056
        if (api_get_configuration_value('allow_exercise_categories')) {
10057
            $manager = new ExerciseCategoryManager();
10058
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10059
            if (!empty($options)) {
10060
                $form->addSelect(
10061
                    'category_id',
10062
                    get_lang('Category'),
10063
                    $options,
10064
                    ['placeholder' => get_lang('SelectAnOption')]
10065
                );
10066
            }
10067
        }
10068
10069
        $form->addButtonSearch(get_lang('Search'));
10070
        $return = $form->returnForm();
10071
10072
        $return .= '<ul class="lp_resource">';
10073
10074
        $return .= '<li class="lp_resource_element">';
10075
        $return .= Display::return_icon('new_exercice.png');
10076
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10077
            get_lang('NewExercise').'</a>';
10078
        $return .= '</li>';
10079
10080
        $previewIcon = Display::return_icon(
10081
            'preview_view.png',
10082
            get_lang('Preview')
10083
        );
10084
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10085
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10086
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10087
10088
        // Display hotpotatoes
10089
        while ($row_hot = Database::fetch_array($res_hot)) {
10090
            $link = Display::url(
10091
                $previewIcon,
10092
                $exerciseUrl.'&file='.$row_hot['path'],
10093
                ['target' => '_blank']
10094
            );
10095
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10096
            $return .= '<a class="moved" href="#">';
10097
            $return .= Display::return_icon(
10098
                'move_everywhere.png',
10099
                get_lang('Move'),
10100
                [],
10101
                ICON_SIZE_TINY
10102
            );
10103
            $return .= '</a> ';
10104
            $return .= Display::return_icon('hotpotatoes_s.png');
10105
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10106
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10107
            $return .= '</li>';
10108
        }
10109
10110
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10111
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10112
            $title = strip_tags(
10113
                api_html_entity_decode($row_quiz['title'])
10114
            );
10115
10116
            $visibility = api_get_item_visibility(
10117
                ['real_id' => $course_id],
10118
                TOOL_QUIZ,
10119
                $row_quiz['iid'],
10120
                $session_id
10121
            );
10122
10123
            $link = Display::url(
10124
                $previewIcon,
10125
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10126
                ['target' => '_blank']
10127
            );
10128
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10129
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10130
            $return .= $quizIcon;
10131
            $sessionStar = api_get_session_image(
10132
                $row_quiz['session_id'],
10133
                $userInfo['status']
10134
            );
10135
            $return .= Display::url(
10136
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10137
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10138
                [
10139
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10140
                ]
10141
            );
10142
            $return .= '</li>';
10143
        }
10144
10145
        $return .= '</ul>';
10146
10147
        return $return;
10148
    }
10149
10150
    /**
10151
     * Creates a list with all the links in it.
10152
     *
10153
     * @return string
10154
     */
10155
    public function get_links()
10156
    {
10157
        $selfUrl = api_get_self();
10158
        $courseIdReq = api_get_cidreq();
10159
        $course = api_get_course_info();
10160
        $userInfo = api_get_user_info();
10161
10162
        $course_id = $course['real_id'];
10163
        $tbl_link = Database::get_course_table(TABLE_LINK);
10164
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10165
        $moveEverywhereIcon = Display::return_icon(
10166
            'move_everywhere.png',
10167
            get_lang('Move'),
10168
            [],
10169
            ICON_SIZE_TINY
10170
        );
10171
10172
        $session_id = api_get_session_id();
10173
        $condition_session = api_get_session_condition(
10174
            $session_id,
10175
            true,
10176
            true,
10177
            'link.session_id'
10178
        );
10179
10180
        $sql = "SELECT 
10181
                    link.id as link_id,
10182
                    link.title as link_title,
10183
                    link.session_id as link_session_id,
10184
                    link.category_id as category_id,
10185
                    link_category.category_title as category_title
10186
                FROM $tbl_link as link
10187
                LEFT JOIN $linkCategoryTable as link_category
10188
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10189
                WHERE link.c_id = $course_id $condition_session
10190
                ORDER BY link_category.category_title ASC, link.title ASC";
10191
        $result = Database::query($sql);
10192
        $categorizedLinks = [];
10193
        $categories = [];
10194
10195
        while ($link = Database::fetch_array($result)) {
10196
            if (!$link['category_id']) {
10197
                $link['category_title'] = get_lang('Uncategorized');
10198
            }
10199
            $categories[$link['category_id']] = $link['category_title'];
10200
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10201
        }
10202
10203
        $linksHtmlCode =
10204
            '<script>
10205
            function toggle_tool(tool, id) {
10206
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10207
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10208
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10209
                } else {
10210
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10211
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10212
                }
10213
            }
10214
        </script>
10215
10216
        <ul class="lp_resource">
10217
            <li class="lp_resource_element">
10218
                '.Display::return_icon('linksnew.gif').'
10219
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10220
                get_lang('LinkAdd').'
10221
                </a>
10222
            </li>';
10223
10224
        foreach ($categorizedLinks as $categoryId => $links) {
10225
            $linkNodes = null;
10226
            foreach ($links as $key => $linkInfo) {
10227
                $title = $linkInfo['link_title'];
10228
                $linkSessionId = $linkInfo['link_session_id'];
10229
10230
                $link = Display::url(
10231
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10232
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10233
                    ['target' => '_blank']
10234
                );
10235
10236
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10237
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10238
                    $linkNodes .=
10239
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10240
                        <a class="moved" href="#">'.
10241
                            $moveEverywhereIcon.
10242
                        '</a>
10243
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10244
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10245
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10246
                        Security::remove_XSS($title).$sessionStar.$link.
10247
                        '</a>
10248
                    </li>';
10249
                }
10250
            }
10251
            $linksHtmlCode .=
10252
                '<li>
10253
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10254
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10255
                    align="absbottom" />
10256
                </a>
10257
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10258
            </li>
10259
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10260
        }
10261
        $linksHtmlCode .= '</ul>';
10262
10263
        return $linksHtmlCode;
10264
    }
10265
10266
    /**
10267
     * Creates a list with all the student publications in it.
10268
     *
10269
     * @return string
10270
     */
10271
    public function get_student_publications()
10272
    {
10273
        $return = '<ul class="lp_resource">';
10274
        $return .= '<li class="lp_resource_element">';
10275
        $return .= Display::return_icon('works_new.gif');
10276
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10277
            get_lang('AddAssignmentPage').'</a>';
10278
        $return .= '</li>';
10279
10280
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10281
        $works = getWorkListTeacher(0, 100, null, null, null);
10282
        if (!empty($works)) {
10283
            foreach ($works as $work) {
10284
                $link = Display::url(
10285
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10286
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10287
                    ['target' => '_blank']
10288
                );
10289
10290
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10291
                $return .= '<a class="moved" href="#">';
10292
                $return .= Display::return_icon(
10293
                    'move_everywhere.png',
10294
                    get_lang('Move'),
10295
                    [],
10296
                    ICON_SIZE_TINY
10297
                );
10298
                $return .= '</a> ';
10299
10300
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10301
                $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.'">'.
10302
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10303
                </a>';
10304
10305
                $return .= '</li>';
10306
            }
10307
        }
10308
10309
        $return .= '</ul>';
10310
10311
        return $return;
10312
    }
10313
10314
    /**
10315
     * Creates a list with all the forums in it.
10316
     *
10317
     * @return string
10318
     */
10319
    public function get_forums()
10320
    {
10321
        require_once '../forum/forumfunction.inc.php';
10322
10323
        $forumCategories = get_forum_categories();
10324
        $forumsInNoCategory = get_forums_in_category(0);
10325
        if (!empty($forumsInNoCategory)) {
10326
            $forumCategories = array_merge(
10327
                $forumCategories,
10328
                [
10329
                    [
10330
                        'cat_id' => 0,
10331
                        'session_id' => 0,
10332
                        'visibility' => 1,
10333
                        'cat_comment' => null,
10334
                    ],
10335
                ]
10336
            );
10337
        }
10338
10339
        $forumList = get_forums();
10340
        $a_forums = [];
10341
        foreach ($forumCategories as $forumCategory) {
10342
            // The forums in this category.
10343
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10344
            if (!empty($forumsInCategory)) {
10345
                foreach ($forumList as $forum) {
10346
                    if (isset($forum['forum_category']) &&
10347
                        $forum['forum_category'] == $forumCategory['cat_id']
10348
                    ) {
10349
                        $a_forums[] = $forum;
10350
                    }
10351
                }
10352
            }
10353
        }
10354
10355
        $return = '<ul class="lp_resource">';
10356
10357
        // First add link
10358
        $return .= '<li class="lp_resource_element">';
10359
        $return .= Display::return_icon('new_forum.png');
10360
        $return .= Display::url(
10361
            get_lang('CreateANewForum'),
10362
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10363
                'action' => 'add',
10364
                'content' => 'forum',
10365
                'lp_id' => $this->lp_id,
10366
            ]),
10367
            ['title' => get_lang('CreateANewForum')]
10368
        );
10369
        $return .= '</li>';
10370
10371
        $return .= '<script>
10372
            function toggle_forum(forum_id) {
10373
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10374
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10375
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10376
                } else {
10377
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10378
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10379
                }
10380
            }
10381
        </script>';
10382
10383
        foreach ($a_forums as $forum) {
10384
            if (!empty($forum['forum_id'])) {
10385
                $link = Display::url(
10386
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10387
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10388
                    ['target' => '_blank']
10389
                );
10390
10391
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10392
                $return .= '<a class="moved" href="#">';
10393
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10394
                $return .= ' </a>';
10395
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10396
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10397
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10398
                            </a>
10399
                            <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">'.
10400
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10401
10402
                $return .= '</li>';
10403
10404
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10405
                $a_threads = get_threads($forum['forum_id']);
10406
                if (is_array($a_threads)) {
10407
                    foreach ($a_threads as $thread) {
10408
                        $link = Display::url(
10409
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10410
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10411
                            ['target' => '_blank']
10412
                        );
10413
10414
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10415
                        $return .= '&nbsp;<a class="moved" href="#">';
10416
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10417
                        $return .= ' </a>';
10418
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10419
                        $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.'">'.
10420
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10421
                        $return .= '</li>';
10422
                    }
10423
                }
10424
                $return .= '</div>';
10425
            }
10426
        }
10427
        $return .= '</ul>';
10428
10429
        return $return;
10430
    }
10431
10432
    /**
10433
     * // TODO: The output encoding should be equal to the system encoding.
10434
     *
10435
     * Exports the learning path as a SCORM package. This is the main function that
10436
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10437
     * whole thing and returns the zip.
10438
     *
10439
     * This method needs to be called in PHP5, as it will fail with non-adequate
10440
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10441
     * you need to call it on a learnpath object.
10442
     *
10443
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10444
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10445
     * path has been modified, it should use the generic method here below.
10446
     *
10447
     * @return string Returns the zip package string, or null if error
10448
     */
10449
    public function scormExport()
10450
    {
10451
        api_set_more_memory_and_time_limits();
10452
10453
        $_course = api_get_course_info();
10454
        $course_id = $_course['real_id'];
10455
        // Create the zip handler (this will remain available throughout the method).
10456
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10457
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10458
        $temp_dir_short = uniqid('scorm_export', true);
10459
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10460
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10461
        $zip_folder = new PclZip($temp_zip_file);
10462
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10463
        $root_path = $main_path = api_get_path(SYS_PATH);
10464
        $files_cleanup = [];
10465
10466
        // Place to temporarily stash the zip file.
10467
        // create the temp dir if it doesn't exist
10468
        // or do a cleanup before creating the zip file.
10469
        if (!is_dir($temp_zip_dir)) {
10470
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10471
        } else {
10472
            // Cleanup: Check the temp dir for old files and delete them.
10473
            $handle = opendir($temp_zip_dir);
10474
            while (false !== ($file = readdir($handle))) {
10475
                if ($file != '.' && $file != '..') {
10476
                    unlink("$temp_zip_dir/$file");
10477
                }
10478
            }
10479
            closedir($handle);
10480
        }
10481
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10482
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10483
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10484
        ) {
10485
            // Remove the possible . at the end of the path.
10486
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10487
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10488
            mkdir(
10489
                $dest_path_to_scorm_folder,
10490
                api_get_permissions_for_new_directories(),
10491
                true
10492
            );
10493
            copyr(
10494
                $current_course_path.'/scorm/'.$this->path,
10495
                $dest_path_to_scorm_folder,
10496
                ['imsmanifest'],
10497
                $zip_files
10498
            );
10499
        }
10500
10501
        // Build a dummy imsmanifest structure.
10502
        // Do not add to the zip yet (we still need it).
10503
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10504
        // Aggregation Model official document, section "2.3 Content Packaging".
10505
        // We are going to build a UTF-8 encoded manifest.
10506
        // Later we will recode it to the desired (and supported) encoding.
10507
        $xmldoc = new DOMDocument('1.0');
10508
        $root = $xmldoc->createElement('manifest');
10509
        $root->setAttribute('identifier', 'SingleCourseManifest');
10510
        $root->setAttribute('version', '1.1');
10511
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10512
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10513
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10514
        $root->setAttribute(
10515
            'xsi:schemaLocation',
10516
            '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'
10517
        );
10518
        // Build mandatory sub-root container elements.
10519
        $metadata = $xmldoc->createElement('metadata');
10520
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10521
        $metadata->appendChild($md_schema);
10522
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10523
        $metadata->appendChild($md_schemaversion);
10524
        $root->appendChild($metadata);
10525
10526
        $organizations = $xmldoc->createElement('organizations');
10527
        $resources = $xmldoc->createElement('resources');
10528
10529
        // Build the only organization we will use in building our learnpaths.
10530
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10531
        $organization = $xmldoc->createElement('organization');
10532
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10533
        // To set the title of the SCORM entity (=organization), we take the name given
10534
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10535
        // learning path charset) as it is the encoding that defines how it is stored
10536
        // in the database. Then we convert it to HTML entities again as the "&" character
10537
        // alone is not authorized in XML (must be &amp;).
10538
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10539
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10540
        $organization->appendChild($org_title);
10541
        $folder_name = 'document';
10542
10543
        // Removes the learning_path/scorm_folder path when exporting see #4841
10544
        $path_to_remove = '';
10545
        $path_to_replace = '';
10546
        $result = $this->generate_lp_folder($_course);
10547
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10548
            $path_to_remove = 'document'.$result['dir'];
10549
            $path_to_replace = $folder_name.'/';
10550
        }
10551
10552
        // Fixes chamilo scorm exports
10553
        if ($this->ref === 'chamilo_scorm_export') {
10554
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10555
        }
10556
10557
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10558
        $link_updates = [];
10559
        $links_to_create = [];
10560
        foreach ($this->ordered_items as $index => $itemId) {
10561
            /** @var learnpathItem $item */
10562
            $item = $this->items[$itemId];
10563
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10564
                // Get included documents from this item.
10565
                if ($item->type === 'sco') {
10566
                    $inc_docs = $item->get_resources_from_source(
10567
                        null,
10568
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10569
                    );
10570
                } else {
10571
                    $inc_docs = $item->get_resources_from_source();
10572
                }
10573
10574
                // Give a child element <item> to the <organization> element.
10575
                $my_item_id = $item->get_id();
10576
                $my_item = $xmldoc->createElement('item');
10577
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10578
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10579
                $my_item->setAttribute('isvisible', 'true');
10580
                // Give a child element <title> to the <item> element.
10581
                $my_title = $xmldoc->createElement(
10582
                    'title',
10583
                    htmlspecialchars(
10584
                        api_utf8_encode($item->get_title()),
10585
                        ENT_QUOTES,
10586
                        'UTF-8'
10587
                    )
10588
                );
10589
                $my_item->appendChild($my_title);
10590
                // Give a child element <adlcp:prerequisites> to the <item> element.
10591
                $my_prereqs = $xmldoc->createElement(
10592
                    'adlcp:prerequisites',
10593
                    $this->get_scorm_prereq_string($my_item_id)
10594
                );
10595
                $my_prereqs->setAttribute('type', 'aicc_script');
10596
                $my_item->appendChild($my_prereqs);
10597
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10598
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10599
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10600
                //$xmldoc->createElement('adlcp:timelimitaction','');
10601
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10602
                //$xmldoc->createElement('adlcp:datafromlms','');
10603
                // Give a child element <adlcp:masteryscore> to the <item> element.
10604
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10605
                $my_item->appendChild($my_masteryscore);
10606
10607
                // Attach this item to the organization element or hits parent if there is one.
10608
                if (!empty($item->parent) && $item->parent != 0) {
10609
                    $children = $organization->childNodes;
10610
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10611
                    if (is_object($possible_parent)) {
10612
                        $possible_parent->appendChild($my_item);
10613
                    } else {
10614
                        if ($this->debug > 0) {
10615
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10616
                        }
10617
                    }
10618
                } else {
10619
                    if ($this->debug > 0) {
10620
                        error_log('No parent');
10621
                    }
10622
                    $organization->appendChild($my_item);
10623
                }
10624
10625
                // Get the path of the file(s) from the course directory root.
10626
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10627
                $my_xml_file_path = $my_file_path;
10628
                if (!empty($path_to_remove)) {
10629
                    // From docs
10630
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10631
10632
                    // From quiz
10633
                    if ($this->ref === 'chamilo_scorm_export') {
10634
                        $path_to_remove = 'scorm/'.$this->path.'/';
10635
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10636
                    }
10637
                }
10638
10639
                $my_sub_dir = dirname($my_file_path);
10640
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10641
                $my_xml_sub_dir = $my_sub_dir;
10642
                // Give a <resource> child to the <resources> element
10643
                $my_resource = $xmldoc->createElement('resource');
10644
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10645
                $my_resource->setAttribute('type', 'webcontent');
10646
                $my_resource->setAttribute('href', $my_xml_file_path);
10647
                // adlcp:scormtype can be either 'sco' or 'asset'.
10648
                if ($item->type === 'sco') {
10649
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10650
                } else {
10651
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10652
                }
10653
                // xml:base is the base directory to find the files declared in this resource.
10654
                $my_resource->setAttribute('xml:base', '');
10655
                // Give a <file> child to the <resource> element.
10656
                $my_file = $xmldoc->createElement('file');
10657
                $my_file->setAttribute('href', $my_xml_file_path);
10658
                $my_resource->appendChild($my_file);
10659
10660
                // Dependency to other files - not yet supported.
10661
                $i = 1;
10662
                if ($inc_docs) {
10663
                    foreach ($inc_docs as $doc_info) {
10664
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10665
                            continue;
10666
                        }
10667
                        $my_dep = $xmldoc->createElement('resource');
10668
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10669
                        $my_dep->setAttribute('identifier', $res_id);
10670
                        $my_dep->setAttribute('type', 'webcontent');
10671
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10672
                        $my_dep_file = $xmldoc->createElement('file');
10673
                        // Check type of URL.
10674
                        if ($doc_info[1] == 'remote') {
10675
                            // Remote file. Save url as is.
10676
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10677
                            $my_dep->setAttribute('xml:base', '');
10678
                        } elseif ($doc_info[1] === 'local') {
10679
                            switch ($doc_info[2]) {
10680
                                case 'url':
10681
                                    // Local URL - save path as url for now, don't zip file.
10682
                                    $abs_path = api_get_path(SYS_PATH).
10683
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10684
                                    $current_dir = dirname($abs_path);
10685
                                    $current_dir = str_replace('\\', '/', $current_dir);
10686
                                    $file_path = realpath($abs_path);
10687
                                    $file_path = str_replace('\\', '/', $file_path);
10688
                                    $my_dep_file->setAttribute('href', $file_path);
10689
                                    $my_dep->setAttribute('xml:base', '');
10690
                                    if (strstr($file_path, $main_path) !== false) {
10691
                                        // The calculated real path is really inside Chamilo's root path.
10692
                                        // Reduce file path to what's under the DocumentRoot.
10693
                                        $replace = $file_path;
10694
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10695
                                        $destinationFile = $file_path;
10696
10697
                                        if (strstr($file_path, 'upload/users') !== false) {
10698
                                            $pos = strpos($file_path, 'my_files/');
10699
                                            if ($pos !== false) {
10700
                                                $onlyDirectory = str_replace(
10701
                                                    'upload/users/',
10702
                                                    '',
10703
                                                    substr($file_path, $pos, strlen($file_path))
10704
                                                );
10705
                                            }
10706
                                            $replace = $onlyDirectory;
10707
                                            $destinationFile = $replace;
10708
                                        }
10709
                                        $zip_files_abs[] = $file_path;
10710
                                        $link_updates[$my_file_path][] = [
10711
                                            'orig' => $doc_info[0],
10712
                                            'dest' => $destinationFile,
10713
                                            'replace' => $replace,
10714
                                        ];
10715
                                        $my_dep_file->setAttribute('href', $file_path);
10716
                                        $my_dep->setAttribute('xml:base', '');
10717
                                    } elseif (empty($file_path)) {
10718
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10719
                                        $file_path = str_replace('//', '/', $file_path);
10720
                                        if (file_exists($file_path)) {
10721
                                            // We get the relative path.
10722
                                            $file_path = substr($file_path, strlen($current_dir));
10723
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10724
                                            $link_updates[$my_file_path][] = [
10725
                                                'orig' => $doc_info[0],
10726
                                                'dest' => $file_path,
10727
                                            ];
10728
                                            $my_dep_file->setAttribute('href', $file_path);
10729
                                            $my_dep->setAttribute('xml:base', '');
10730
                                        }
10731
                                    }
10732
                                    break;
10733
                                case 'abs':
10734
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10735
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10736
                                    $my_dep->setAttribute('xml:base', '');
10737
10738
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10739
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10740
                                    $abs_img_path_without_subdir = $doc_info[0];
10741
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10742
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10743
                                    if ($pos === 0) {
10744
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10745
                                    }
10746
10747
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10748
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10749
10750
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10751
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10752
                                    // Check if the current document is in that path.
10753
                                    if (strstr($file_path, $cur_path) !== false) {
10754
                                        $destinationFile = substr($file_path, strlen($cur_path));
10755
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10756
10757
                                        $fileToTest = $cur_path.$my_file_path;
10758
                                        if (!empty($path_to_remove)) {
10759
                                            $fileToTest = str_replace(
10760
                                                $path_to_remove.'/',
10761
                                                $path_to_replace,
10762
                                                $cur_path.$my_file_path
10763
                                            );
10764
                                        }
10765
10766
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10767
10768
                                        // Put the current document in the zip (this array is the array
10769
                                        // that will manage documents already in the course folder - relative).
10770
                                        $zip_files[] = $filePathNoCoursePart;
10771
                                        // Update the links to the current document in the
10772
                                        // containing document (make them relative).
10773
                                        $link_updates[$my_file_path][] = [
10774
                                            'orig' => $doc_info[0],
10775
                                            'dest' => $destinationFile,
10776
                                            'replace' => $relative_path,
10777
                                        ];
10778
10779
                                        $my_dep_file->setAttribute('href', $file_path);
10780
                                        $my_dep->setAttribute('xml:base', '');
10781
                                    } elseif (strstr($file_path, $main_path) !== false) {
10782
                                        // The calculated real path is really inside Chamilo's root path.
10783
                                        // Reduce file path to what's under the DocumentRoot.
10784
                                        $file_path = substr($file_path, strlen($root_path));
10785
                                        $zip_files_abs[] = $file_path;
10786
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10787
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10788
                                        $my_dep->setAttribute('xml:base', '');
10789
                                    } elseif (empty($file_path)) {
10790
                                        // Probably this is an image inside "/main" directory
10791
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10792
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10793
10794
                                        if (file_exists($file_path)) {
10795
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10796
                                                // We get the relative path.
10797
                                                $pos = strpos($file_path, 'main/default_course_document/');
10798
                                                if ($pos !== false) {
10799
                                                    $onlyDirectory = str_replace(
10800
                                                        'main/default_course_document/',
10801
                                                        '',
10802
                                                        substr($file_path, $pos, strlen($file_path))
10803
                                                    );
10804
                                                }
10805
10806
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10807
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10808
                                                $zip_files_abs[] = $fileAbs;
10809
                                                $link_updates[$my_file_path][] = [
10810
                                                    'orig' => $doc_info[0],
10811
                                                    'dest' => $destinationFile,
10812
                                                ];
10813
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10814
                                                $my_dep->setAttribute('xml:base', '');
10815
                                            }
10816
                                        }
10817
                                    }
10818
                                    break;
10819
                                case 'rel':
10820
                                    // Path relative to the current document.
10821
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10822
                                    if (substr($doc_info[0], 0, 2) === '..') {
10823
                                        // Relative path going up.
10824
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10825
                                        $current_dir = str_replace('\\', '/', $current_dir);
10826
                                        $file_path = realpath($current_dir.$doc_info[0]);
10827
                                        $file_path = str_replace('\\', '/', $file_path);
10828
                                        if (strstr($file_path, $main_path) !== false) {
10829
                                            // The calculated real path is really inside Chamilo's root path.
10830
                                            // Reduce file path to what's under the DocumentRoot.
10831
                                            $file_path = substr($file_path, strlen($root_path));
10832
                                            $zip_files_abs[] = $file_path;
10833
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10834
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10835
                                            $my_dep->setAttribute('xml:base', '');
10836
                                        }
10837
                                    } else {
10838
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10839
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10840
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10841
                                    }
10842
                                    break;
10843
                                default:
10844
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10845
                                    $my_dep->setAttribute('xml:base', '');
10846
                                    break;
10847
                            }
10848
                        }
10849
                        $my_dep->appendChild($my_dep_file);
10850
                        $resources->appendChild($my_dep);
10851
                        $dependency = $xmldoc->createElement('dependency');
10852
                        $dependency->setAttribute('identifierref', $res_id);
10853
                        $my_resource->appendChild($dependency);
10854
                        $i++;
10855
                    }
10856
                }
10857
                $resources->appendChild($my_resource);
10858
                $zip_files[] = $my_file_path;
10859
            } else {
10860
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10861
                switch ($item->type) {
10862
                    case TOOL_LINK:
10863
                        $my_item = $xmldoc->createElement('item');
10864
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10865
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10866
                        $my_item->setAttribute('isvisible', 'true');
10867
                        // Give a child element <title> to the <item> element.
10868
                        $my_title = $xmldoc->createElement(
10869
                            'title',
10870
                            htmlspecialchars(
10871
                                api_utf8_encode($item->get_title()),
10872
                                ENT_QUOTES,
10873
                                'UTF-8'
10874
                            )
10875
                        );
10876
                        $my_item->appendChild($my_title);
10877
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10878
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10879
                        $my_prereqs->setAttribute('type', 'aicc_script');
10880
                        $my_item->appendChild($my_prereqs);
10881
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10882
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10883
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10884
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10885
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10886
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10887
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10888
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10889
                        $my_item->appendChild($my_masteryscore);
10890
10891
                        // Attach this item to the organization element or its parent if there is one.
10892
                        if (!empty($item->parent) && $item->parent != 0) {
10893
                            $children = $organization->childNodes;
10894
                            for ($i = 0; $i < $children->length; $i++) {
10895
                                $item_temp = $children->item($i);
10896
                                if ($item_temp->nodeName == 'item') {
10897
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10898
                                        $item_temp->appendChild($my_item);
10899
                                    }
10900
                                }
10901
                            }
10902
                        } else {
10903
                            $organization->appendChild($my_item);
10904
                        }
10905
10906
                        $my_file_path = 'link_'.$item->get_id().'.html';
10907
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10908
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10909
                        $rs = Database::query($sql);
10910
                        if ($link = Database::fetch_array($rs)) {
10911
                            $url = $link['url'];
10912
                            $title = stripslashes($link['title']);
10913
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10914
                            $my_xml_file_path = $my_file_path;
10915
                            $my_sub_dir = dirname($my_file_path);
10916
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10917
                            $my_xml_sub_dir = $my_sub_dir;
10918
                            // Give a <resource> child to the <resources> element.
10919
                            $my_resource = $xmldoc->createElement('resource');
10920
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10921
                            $my_resource->setAttribute('type', 'webcontent');
10922
                            $my_resource->setAttribute('href', $my_xml_file_path);
10923
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10924
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10925
                            // xml:base is the base directory to find the files declared in this resource.
10926
                            $my_resource->setAttribute('xml:base', '');
10927
                            // give a <file> child to the <resource> element.
10928
                            $my_file = $xmldoc->createElement('file');
10929
                            $my_file->setAttribute('href', $my_xml_file_path);
10930
                            $my_resource->appendChild($my_file);
10931
                            $resources->appendChild($my_resource);
10932
                        }
10933
                        break;
10934
                    case TOOL_QUIZ:
10935
                        $exe_id = $item->path;
10936
                        // Should be using ref when everything will be cleaned up in this regard.
10937
                        $exe = new Exercise();
10938
                        $exe->read($exe_id);
10939
                        $my_item = $xmldoc->createElement('item');
10940
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10941
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10942
                        $my_item->setAttribute('isvisible', 'true');
10943
                        // Give a child element <title> to the <item> element.
10944
                        $my_title = $xmldoc->createElement(
10945
                            'title',
10946
                            htmlspecialchars(
10947
                                api_utf8_encode($item->get_title()),
10948
                                ENT_QUOTES,
10949
                                'UTF-8'
10950
                            )
10951
                        );
10952
                        $my_item->appendChild($my_title);
10953
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10954
                        $my_item->appendChild($my_max_score);
10955
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10956
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10957
                        $my_prereqs->setAttribute('type', 'aicc_script');
10958
                        $my_item->appendChild($my_prereqs);
10959
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10960
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10961
                        $my_item->appendChild($my_masteryscore);
10962
10963
                        // Attach this item to the organization element or hits parent if there is one.
10964
                        if (!empty($item->parent) && $item->parent != 0) {
10965
                            $children = $organization->childNodes;
10966
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10967
                            if ($possible_parent) {
10968
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10969
                                    $possible_parent->appendChild($my_item);
10970
                                }
10971
                            }
10972
                        } else {
10973
                            $organization->appendChild($my_item);
10974
                        }
10975
10976
                        // Get the path of the file(s) from the course directory root
10977
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10978
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10979
                        // Write the contents of the exported exercise into a (big) html file
10980
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10981
                        $scormExercise = new ScormExercise($exe, true);
10982
                        $contents = $scormExercise->export();
10983
10984
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10985
                        $res = file_put_contents($tmp_file_path, $contents);
10986
                        if ($res === false) {
10987
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10988
                        }
10989
                        $files_cleanup[] = $tmp_file_path;
10990
                        $my_xml_file_path = $my_file_path;
10991
                        $my_sub_dir = dirname($my_file_path);
10992
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10993
                        $my_xml_sub_dir = $my_sub_dir;
10994
                        // Give a <resource> child to the <resources> element.
10995
                        $my_resource = $xmldoc->createElement('resource');
10996
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10997
                        $my_resource->setAttribute('type', 'webcontent');
10998
                        $my_resource->setAttribute('href', $my_xml_file_path);
10999
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11000
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11001
                        // xml:base is the base directory to find the files declared in this resource.
11002
                        $my_resource->setAttribute('xml:base', '');
11003
                        // Give a <file> child to the <resource> element.
11004
                        $my_file = $xmldoc->createElement('file');
11005
                        $my_file->setAttribute('href', $my_xml_file_path);
11006
                        $my_resource->appendChild($my_file);
11007
11008
                        // Get included docs.
11009
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11010
11011
                        // Dependency to other files - not yet supported.
11012
                        $i = 1;
11013
                        foreach ($inc_docs as $doc_info) {
11014
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11015
                                continue;
11016
                            }
11017
                            $my_dep = $xmldoc->createElement('resource');
11018
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11019
                            $my_dep->setAttribute('identifier', $res_id);
11020
                            $my_dep->setAttribute('type', 'webcontent');
11021
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11022
                            $my_dep_file = $xmldoc->createElement('file');
11023
                            // Check type of URL.
11024
                            if ($doc_info[1] == 'remote') {
11025
                                // Remote file. Save url as is.
11026
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11027
                                $my_dep->setAttribute('xml:base', '');
11028
                            } elseif ($doc_info[1] == 'local') {
11029
                                switch ($doc_info[2]) {
11030
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11031
                                        // Save file but as local file (retrieve from URL).
11032
                                        $abs_path = api_get_path(SYS_PATH).
11033
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11034
                                        $current_dir = dirname($abs_path);
11035
                                        $current_dir = str_replace('\\', '/', $current_dir);
11036
                                        $file_path = realpath($abs_path);
11037
                                        $file_path = str_replace('\\', '/', $file_path);
11038
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11039
                                        $my_dep->setAttribute('xml:base', '');
11040
                                        if (strstr($file_path, $main_path) !== false) {
11041
                                            // The calculated real path is really inside the chamilo root path.
11042
                                            // Reduce file path to what's under the DocumentRoot.
11043
                                            $file_path = substr($file_path, strlen($root_path));
11044
                                            $zip_files_abs[] = $file_path;
11045
                                            $link_updates[$my_file_path][] = [
11046
                                                'orig' => $doc_info[0],
11047
                                                'dest' => 'document/'.$file_path,
11048
                                            ];
11049
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11050
                                            $my_dep->setAttribute('xml:base', '');
11051
                                        } elseif (empty($file_path)) {
11052
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11053
                                            $file_path = str_replace('//', '/', $file_path);
11054
                                            if (file_exists($file_path)) {
11055
                                                $file_path = substr($file_path, strlen($current_dir));
11056
                                                // We get the relative path.
11057
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11058
                                                $link_updates[$my_file_path][] = [
11059
                                                    'orig' => $doc_info[0],
11060
                                                    'dest' => 'document/'.$file_path,
11061
                                                ];
11062
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11063
                                                $my_dep->setAttribute('xml:base', '');
11064
                                            }
11065
                                        }
11066
                                        break;
11067
                                    case 'abs':
11068
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11069
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11070
                                        $current_dir = str_replace('\\', '/', $current_dir);
11071
                                        $file_path = realpath($doc_info[0]);
11072
                                        $file_path = str_replace('\\', '/', $file_path);
11073
                                        $my_dep_file->setAttribute('href', $file_path);
11074
                                        $my_dep->setAttribute('xml:base', '');
11075
11076
                                        if (strstr($file_path, $main_path) !== false) {
11077
                                            // The calculated real path is really inside the chamilo root path.
11078
                                            // Reduce file path to what's under the DocumentRoot.
11079
                                            $file_path = substr($file_path, strlen($root_path));
11080
                                            $zip_files_abs[] = $file_path;
11081
                                            $link_updates[$my_file_path][] = [
11082
                                                'orig' => $doc_info[0],
11083
                                                'dest' => $file_path,
11084
                                            ];
11085
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11086
                                            $my_dep->setAttribute('xml:base', '');
11087
                                        } elseif (empty($file_path)) {
11088
                                            $docSysPartPath = str_replace(
11089
                                                api_get_path(REL_COURSE_PATH),
11090
                                                '',
11091
                                                $doc_info[0]
11092
                                            );
11093
11094
                                            $docSysPartPathNoCourseCode = str_replace(
11095
                                                $_course['directory'].'/',
11096
                                                '',
11097
                                                $docSysPartPath
11098
                                            );
11099
11100
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11101
                                            if (file_exists($docSysPath)) {
11102
                                                $file_path = $docSysPartPathNoCourseCode;
11103
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11104
                                                $link_updates[$my_file_path][] = [
11105
                                                    'orig' => $doc_info[0],
11106
                                                    'dest' => $file_path,
11107
                                                ];
11108
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11109
                                                $my_dep->setAttribute('xml:base', '');
11110
                                            }
11111
                                        }
11112
                                        break;
11113
                                    case 'rel':
11114
                                        // Path relative to the current document. Save xml:base as current document's
11115
                                        // directory and save file in zip as subdir.file_path
11116
                                        if (substr($doc_info[0], 0, 2) === '..') {
11117
                                            // Relative path going up.
11118
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11119
                                            $current_dir = str_replace('\\', '/', $current_dir);
11120
                                            $file_path = realpath($current_dir.$doc_info[0]);
11121
                                            $file_path = str_replace('\\', '/', $file_path);
11122
                                            if (strstr($file_path, $main_path) !== false) {
11123
                                                // The calculated real path is really inside Chamilo's root path.
11124
                                                // Reduce file path to what's under the DocumentRoot.
11125
11126
                                                $file_path = substr($file_path, strlen($root_path));
11127
                                                $file_path_dest = $file_path;
11128
11129
                                                // File path is courses/CHAMILO/document/....
11130
                                                $info_file_path = explode('/', $file_path);
11131
                                                if ($info_file_path[0] == 'courses') {
11132
                                                    // Add character "/" in file path.
11133
                                                    $file_path_dest = 'document/'.$file_path;
11134
                                                }
11135
                                                $zip_files_abs[] = $file_path;
11136
11137
                                                $link_updates[$my_file_path][] = [
11138
                                                    'orig' => $doc_info[0],
11139
                                                    'dest' => $file_path_dest,
11140
                                                ];
11141
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11142
                                                $my_dep->setAttribute('xml:base', '');
11143
                                            }
11144
                                        } else {
11145
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11146
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11147
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11148
                                        }
11149
                                        break;
11150
                                    default:
11151
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11152
                                        $my_dep->setAttribute('xml:base', '');
11153
                                        break;
11154
                                }
11155
                            }
11156
                            $my_dep->appendChild($my_dep_file);
11157
                            $resources->appendChild($my_dep);
11158
                            $dependency = $xmldoc->createElement('dependency');
11159
                            $dependency->setAttribute('identifierref', $res_id);
11160
                            $my_resource->appendChild($dependency);
11161
                            $i++;
11162
                        }
11163
                        $resources->appendChild($my_resource);
11164
                        $zip_files[] = $my_file_path;
11165
                        break;
11166
                    default:
11167
                        // Get the path of the file(s) from the course directory root
11168
                        $my_file_path = 'non_exportable.html';
11169
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11170
                        $my_xml_file_path = $my_file_path;
11171
                        $my_sub_dir = dirname($my_file_path);
11172
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11173
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11174
                        $my_xml_sub_dir = $my_sub_dir;
11175
                        // Give a <resource> child to the <resources> element.
11176
                        $my_resource = $xmldoc->createElement('resource');
11177
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11178
                        $my_resource->setAttribute('type', 'webcontent');
11179
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11180
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11181
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11182
                        // xml:base is the base directory to find the files declared in this resource.
11183
                        $my_resource->setAttribute('xml:base', '');
11184
                        // Give a <file> child to the <resource> element.
11185
                        $my_file = $xmldoc->createElement('file');
11186
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11187
                        $my_resource->appendChild($my_file);
11188
                        $resources->appendChild($my_resource);
11189
                        break;
11190
                }
11191
            }
11192
        }
11193
        $organizations->appendChild($organization);
11194
        $root->appendChild($organizations);
11195
        $root->appendChild($resources);
11196
        $xmldoc->appendChild($root);
11197
11198
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11199
11200
        // then add the file to the zip, then destroy the file (this is done automatically).
11201
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11202
        foreach ($zip_files as $file_path) {
11203
            if (empty($file_path)) {
11204
                continue;
11205
            }
11206
11207
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11208
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11209
11210
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11211
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11212
            }
11213
11214
            $this->create_path($dest_file);
11215
            @copy($filePath, $dest_file);
11216
11217
            // Check if the file needs a link update.
11218
            if (in_array($file_path, array_keys($link_updates))) {
11219
                $string = file_get_contents($dest_file);
11220
                unlink($dest_file);
11221
                foreach ($link_updates[$file_path] as $old_new) {
11222
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11223
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11224
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11225
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11226
                    if (substr($old_new['dest'], -3) === 'flv' &&
11227
                        substr($old_new['dest'], 0, 5) === 'main/'
11228
                    ) {
11229
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11230
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11231
                        substr($old_new['dest'], 0, 6) === 'video/'
11232
                    ) {
11233
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11234
                    }
11235
11236
                    // Fix to avoid problems with default_course_document
11237
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11238
                        $newDestination = $old_new['dest'];
11239
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11240
                            $newDestination = $old_new['replace'];
11241
                        }
11242
                    } else {
11243
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11244
                    }
11245
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11246
11247
                    // Add files inside the HTMLs
11248
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11249
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11250
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11251
                        copy(
11252
                            $sys_course_path.$new_path,
11253
                            $destinationFile
11254
                        );
11255
                    }
11256
                }
11257
                file_put_contents($dest_file, $string);
11258
            }
11259
11260
            if (file_exists($filePath) && $copyAll) {
11261
                $extension = $this->get_extension($filePath);
11262
                if (in_array($extension, ['html', 'html'])) {
11263
                    $containerOrigin = dirname($filePath);
11264
                    $containerDestination = dirname($dest_file);
11265
11266
                    $finder = new Finder();
11267
                    $finder->files()->in($containerOrigin)
11268
                        ->notName('*_DELETED_*')
11269
                        ->exclude('share_folder')
11270
                        ->exclude('chat_files')
11271
                        ->exclude('certificates')
11272
                    ;
11273
11274
                    if (is_dir($containerOrigin) &&
11275
                        is_dir($containerDestination)
11276
                    ) {
11277
                        $fs = new Filesystem();
11278
                        $fs->mirror(
11279
                            $containerOrigin,
11280
                            $containerDestination,
11281
                            $finder
11282
                        );
11283
                    }
11284
                }
11285
            }
11286
        }
11287
11288
        foreach ($zip_files_abs as $file_path) {
11289
            if (empty($file_path)) {
11290
                continue;
11291
            }
11292
11293
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11294
                continue;
11295
            }
11296
11297
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11298
            if (strstr($file_path, 'upload/users') !== false) {
11299
                $pos = strpos($file_path, 'my_files/');
11300
                if ($pos !== false) {
11301
                    $onlyDirectory = str_replace(
11302
                        'upload/users/',
11303
                        '',
11304
                        substr($file_path, $pos, strlen($file_path))
11305
                    );
11306
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11307
                }
11308
            }
11309
11310
            if (strstr($file_path, 'default_course_document/') !== false) {
11311
                $replace = str_replace('/main', '', $file_path);
11312
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11313
            }
11314
11315
            if (empty($dest_file)) {
11316
                continue;
11317
            }
11318
11319
            $this->create_path($dest_file);
11320
            copy($main_path.$file_path, $dest_file);
11321
            // Check if the file needs a link update.
11322
            if (in_array($file_path, array_keys($link_updates))) {
11323
                $string = file_get_contents($dest_file);
11324
                unlink($dest_file);
11325
                foreach ($link_updates[$file_path] as $old_new) {
11326
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11327
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11328
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11329
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11330
                    if (substr($old_new['dest'], -3) == 'flv' &&
11331
                        substr($old_new['dest'], 0, 5) == 'main/'
11332
                    ) {
11333
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11334
                    }
11335
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11336
                }
11337
                file_put_contents($dest_file, $string);
11338
            }
11339
        }
11340
11341
        if (is_array($links_to_create)) {
11342
            foreach ($links_to_create as $file => $link) {
11343
                $content = '<!DOCTYPE html><head>
11344
                            <meta charset="'.api_get_language_isocode().'" />
11345
                            <title>'.$link['title'].'</title>
11346
                            </head>
11347
                            <body dir="'.api_get_text_direction().'">
11348
                            <div style="text-align:center">
11349
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11350
                            </body>
11351
                            </html>';
11352
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11353
            }
11354
        }
11355
11356
        // Add non exportable message explanation.
11357
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11358
        $file_content = '<!DOCTYPE html><head>
11359
                        <meta charset="'.api_get_language_isocode().'" />
11360
                        <title>'.$lang_not_exportable.'</title>
11361
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11362
                        </head>
11363
                        <body dir="'.api_get_text_direction().'">';
11364
        $file_content .=
11365
            <<<EOD
11366
                    <style>
11367
            .error-message {
11368
                font-family: arial, verdana, helvetica, sans-serif;
11369
                border-width: 1px;
11370
                border-style: solid;
11371
                left: 50%;
11372
                margin: 10px auto;
11373
                min-height: 30px;
11374
                padding: 5px;
11375
                right: 50%;
11376
                width: 500px;
11377
                background-color: #FFD1D1;
11378
                border-color: #FF0000;
11379
                color: #000;
11380
            }
11381
        </style>
11382
    <body>
11383
        <div class="error-message">
11384
            $lang_not_exportable
11385
        </div>
11386
    </body>
11387
</html>
11388
EOD;
11389
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11390
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11391
        }
11392
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11393
11394
        // Add the extra files that go along with a SCORM package.
11395
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11396
11397
        $fs = new Filesystem();
11398
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11399
11400
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11401
        $manifest = @$xmldoc->saveXML();
11402
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11403
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11404
        $zip_folder->add(
11405
            $archivePath.'/'.$temp_dir_short,
11406
            PCLZIP_OPT_REMOVE_PATH,
11407
            $archivePath.'/'.$temp_dir_short.'/'
11408
        );
11409
11410
        // Clean possible temporary files.
11411
        foreach ($files_cleanup as $file) {
11412
            $res = unlink($file);
11413
            if ($res === false) {
11414
                error_log(
11415
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11416
                    0
11417
                );
11418
            }
11419
        }
11420
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11421
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11422
    }
11423
11424
    /**
11425
     * @param int $lp_id
11426
     *
11427
     * @return bool
11428
     */
11429
    public function scorm_export_to_pdf($lp_id)
11430
    {
11431
        $lp_id = (int) $lp_id;
11432
        $files_to_export = [];
11433
11434
        $sessionId = api_get_session_id();
11435
        $course_data = api_get_course_info($this->cc);
11436
11437
        if (!empty($course_data)) {
11438
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11439
            $list = self::get_flat_ordered_items_list($lp_id);
11440
            if (!empty($list)) {
11441
                foreach ($list as $item_id) {
11442
                    $item = $this->items[$item_id];
11443
                    switch ($item->type) {
11444
                        case 'document':
11445
                            // Getting documents from a LP with chamilo documents
11446
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11447
                            // Try loading document from the base course.
11448
                            if (empty($file_data) && !empty($sessionId)) {
11449
                                $file_data = DocumentManager::get_document_data_by_id(
11450
                                    $item->path,
11451
                                    $this->cc,
11452
                                    false,
11453
                                    0
11454
                                );
11455
                            }
11456
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11457
                            if (file_exists($file_path)) {
11458
                                $files_to_export[] = [
11459
                                    'title' => $item->get_title(),
11460
                                    'path' => $file_path,
11461
                                ];
11462
                            }
11463
                            break;
11464
                        case 'asset': //commes from a scorm package generated by chamilo
11465
                        case 'sco':
11466
                            $file_path = $scorm_path.'/'.$item->path;
11467
                            if (file_exists($file_path)) {
11468
                                $files_to_export[] = [
11469
                                    'title' => $item->get_title(),
11470
                                    'path' => $file_path,
11471
                                ];
11472
                            }
11473
                            break;
11474
                        case 'dir':
11475
                            $files_to_export[] = [
11476
                                'title' => $item->get_title(),
11477
                                'path' => null,
11478
                            ];
11479
                            break;
11480
                    }
11481
                }
11482
            }
11483
11484
            $pdf = new PDF();
11485
            $result = $pdf->html_to_pdf(
11486
                $files_to_export,
11487
                $this->name,
11488
                $this->cc,
11489
                true,
11490
                true,
11491
                true,
11492
                $this->get_name()
11493
            );
11494
11495
            return $result;
11496
        }
11497
11498
        return false;
11499
    }
11500
11501
    /**
11502
     * Temp function to be moved in main_api or the best place around for this.
11503
     * Creates a file path if it doesn't exist.
11504
     *
11505
     * @param string $path
11506
     */
11507
    public function create_path($path)
11508
    {
11509
        $path_bits = explode('/', dirname($path));
11510
11511
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11512
        $path_built = IS_WINDOWS_OS ? '' : '/';
11513
        foreach ($path_bits as $bit) {
11514
            if (!empty($bit)) {
11515
                $new_path = $path_built.$bit;
11516
                if (is_dir($new_path)) {
11517
                    $path_built = $new_path.'/';
11518
                } else {
11519
                    mkdir($new_path, api_get_permissions_for_new_directories());
11520
                    $path_built = $new_path.'/';
11521
                }
11522
            }
11523
        }
11524
    }
11525
11526
    /**
11527
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11528
     *
11529
     * @return bool The results of the unlink function, or false if there was no image to start with
11530
     */
11531
    public function delete_lp_image()
11532
    {
11533
        $img = $this->get_preview_image();
11534
        if ($img != '') {
11535
            $del_file = $this->get_preview_image_path(null, 'sys');
11536
            if (isset($del_file) && file_exists($del_file)) {
11537
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11538
                if (file_exists($del_file_2)) {
11539
                    unlink($del_file_2);
11540
                }
11541
                $this->set_preview_image('');
11542
11543
                return @unlink($del_file);
11544
            }
11545
        }
11546
11547
        return false;
11548
    }
11549
11550
    /**
11551
     * Uploads an author image to the upload/learning_path/images directory.
11552
     *
11553
     * @param array    The image array, coming from the $_FILES superglobal
11554
     *
11555
     * @return bool True on success, false on error
11556
     */
11557
    public function upload_image($image_array)
11558
    {
11559
        if (!empty($image_array['name'])) {
11560
            $upload_ok = process_uploaded_file($image_array);
11561
            $has_attachment = true;
11562
        }
11563
11564
        if ($upload_ok && $has_attachment) {
11565
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11566
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11567
            $updir = $sys_course_path.$courseDir;
11568
            // Try to add an extension to the file if it hasn't one.
11569
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11570
11571
            if (filter_extension($new_file_name)) {
11572
                $file_extension = explode('.', $image_array['name']);
11573
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11574
                $filename = uniqid('');
11575
                $new_file_name = $filename.'.'.$file_extension;
11576
                $new_path = $updir.'/'.$new_file_name;
11577
11578
                // Resize the image.
11579
                $temp = new Image($image_array['tmp_name']);
11580
                $temp->resize(104);
11581
                $result = $temp->send_image($new_path);
11582
11583
                // Storing the image filename.
11584
                if ($result) {
11585
                    $this->set_preview_image($new_file_name);
11586
11587
                    //Resize to 64px to use on course homepage
11588
                    $temp->resize(64);
11589
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11590
11591
                    return true;
11592
                }
11593
            }
11594
        }
11595
11596
        return false;
11597
    }
11598
11599
    /**
11600
     * @param int    $lp_id
11601
     * @param string $status
11602
     */
11603
    public function set_autolaunch($lp_id, $status)
11604
    {
11605
        $course_id = api_get_course_int_id();
11606
        $lp_id = (int) $lp_id;
11607
        $status = (int) $status;
11608
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11609
11610
        // Setting everything to autolaunch = 0
11611
        $attributes['autolaunch'] = 0;
11612
        $where = [
11613
            'session_id = ? AND c_id = ? ' => [
11614
                api_get_session_id(),
11615
                $course_id,
11616
            ],
11617
        ];
11618
        Database::update($lp_table, $attributes, $where);
11619
        if ($status == 1) {
11620
            //Setting my lp_id to autolaunch = 1
11621
            $attributes['autolaunch'] = 1;
11622
            $where = [
11623
                'iid = ? AND session_id = ? AND c_id = ?' => [
11624
                    $lp_id,
11625
                    api_get_session_id(),
11626
                    $course_id,
11627
                ],
11628
            ];
11629
            Database::update($lp_table, $attributes, $where);
11630
        }
11631
    }
11632
11633
    /**
11634
     * Gets previous_item_id for the next element of the lp_item table.
11635
     *
11636
     * @author Isaac flores paz
11637
     *
11638
     * @return int Previous item ID
11639
     */
11640
    public function select_previous_item_id()
11641
    {
11642
        $course_id = api_get_course_int_id();
11643
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11644
11645
        // Get the max order of the items
11646
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11647
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11648
        $rs_max_order = Database::query($sql);
11649
        $row_max_order = Database::fetch_object($rs_max_order);
11650
        $max_order = $row_max_order->display_order;
11651
        // Get the previous item ID
11652
        $sql = "SELECT iid as previous FROM $table_lp_item
11653
                WHERE 
11654
                    c_id = $course_id AND 
11655
                    lp_id = ".$this->lp_id." AND 
11656
                    display_order = '$max_order' ";
11657
        $rs_max = Database::query($sql);
11658
        $row_max = Database::fetch_object($rs_max);
11659
11660
        // Return the previous item ID
11661
        return $row_max->previous;
11662
    }
11663
11664
    /**
11665
     * Copies an LP.
11666
     */
11667
    public function copy()
11668
    {
11669
        // Course builder
11670
        $cb = new CourseBuilder();
11671
11672
        //Setting tools that will be copied
11673
        $cb->set_tools_to_build(['learnpaths']);
11674
11675
        //Setting elements that will be copied
11676
        $cb->set_tools_specific_id_list(
11677
            ['learnpaths' => [$this->lp_id]]
11678
        );
11679
11680
        $course = $cb->build();
11681
11682
        //Course restorer
11683
        $course_restorer = new CourseRestorer($course);
11684
        $course_restorer->set_add_text_in_items(true);
11685
        $course_restorer->set_tool_copy_settings(
11686
            ['learnpaths' => ['reset_dates' => true]]
11687
        );
11688
        $course_restorer->restore(
11689
            api_get_course_id(),
11690
            api_get_session_id(),
11691
            false,
11692
            false
11693
        );
11694
    }
11695
11696
    /**
11697
     * Verify document size.
11698
     *
11699
     * @param string $s
11700
     *
11701
     * @return bool
11702
     */
11703
    public static function verify_document_size($s)
11704
    {
11705
        $post_max = ini_get('post_max_size');
11706
        if (substr($post_max, -1, 1) == 'M') {
11707
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11708
        } elseif (substr($post_max, -1, 1) == 'G') {
11709
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11710
        }
11711
        $upl_max = ini_get('upload_max_filesize');
11712
        if (substr($upl_max, -1, 1) == 'M') {
11713
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11714
        } elseif (substr($upl_max, -1, 1) == 'G') {
11715
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11716
        }
11717
11718
        $repo = Container::$container->get('Chamilo\CourseBundle\Repository\CDocumentRepository');
11719
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11720
11721
        $course_max_space = DocumentManager::get_course_quota();
11722
        $total_size = filesize($s) + $documents_total_space;
11723
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11724
            return true;
11725
        }
11726
11727
        return false;
11728
    }
11729
11730
    /**
11731
     * Clear LP prerequisites.
11732
     */
11733
    public function clear_prerequisites()
11734
    {
11735
        $course_id = $this->get_course_int_id();
11736
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11737
        $lp_id = $this->get_id();
11738
        // Cleaning prerequisites
11739
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11740
                WHERE c_id = $course_id AND lp_id = $lp_id";
11741
        Database::query($sql);
11742
11743
        // Cleaning mastery score for exercises
11744
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11745
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11746
        Database::query($sql);
11747
    }
11748
11749
    public function set_previous_step_as_prerequisite_for_all_items()
11750
    {
11751
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11752
        $course_id = $this->get_course_int_id();
11753
        $lp_id = $this->get_id();
11754
11755
        if (!empty($this->items)) {
11756
            $previous_item_id = null;
11757
            $previous_item_max = 0;
11758
            $previous_item_type = null;
11759
            $last_item_not_dir = null;
11760
            $last_item_not_dir_type = null;
11761
            $last_item_not_dir_max = null;
11762
11763
            foreach ($this->ordered_items as $itemId) {
11764
                $item = $this->getItem($itemId);
11765
                // if there was a previous item... (otherwise jump to set it)
11766
                if (!empty($previous_item_id)) {
11767
                    $current_item_id = $item->get_id(); //save current id
11768
                    if ($item->get_type() != 'dir') {
11769
                        // Current item is not a folder, so it qualifies to get a prerequisites
11770
                        if ($last_item_not_dir_type == 'quiz') {
11771
                            // if previous is quiz, mark its max score as default score to be achieved
11772
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11773
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11774
                            Database::query($sql);
11775
                        }
11776
                        // now simply update the prerequisite to set it to the last non-chapter item
11777
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11778
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11779
                        Database::query($sql);
11780
                        // record item as 'non-chapter' reference
11781
                        $last_item_not_dir = $item->get_id();
11782
                        $last_item_not_dir_type = $item->get_type();
11783
                        $last_item_not_dir_max = $item->get_max();
11784
                    }
11785
                } else {
11786
                    if ($item->get_type() != 'dir') {
11787
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11788
                        $last_item_not_dir = $item->get_id();
11789
                        $last_item_not_dir_type = $item->get_type();
11790
                        $last_item_not_dir_max = $item->get_max();
11791
                    }
11792
                }
11793
                // Saving the item as "previous item" for the next loop
11794
                $previous_item_id = $item->get_id();
11795
                $previous_item_max = $item->get_max();
11796
                $previous_item_type = $item->get_type();
11797
            }
11798
        }
11799
    }
11800
11801
    /**
11802
     * @param array $params
11803
     *
11804
     * @throws \Doctrine\ORM\OptimisticLockException
11805
     *
11806
     * @return int
11807
     */
11808
    public static function createCategory($params)
11809
    {
11810
        $em = Database::getManager();
11811
        $item = new CLpCategory();
11812
        $item->setName($params['name']);
11813
        $item->setCId($params['c_id']);
11814
        $em->persist($item);
11815
        $em->flush();
11816
11817
        api_item_property_update(
11818
            api_get_course_info(),
11819
            TOOL_LEARNPATH_CATEGORY,
11820
            $item->getId(),
11821
            'visible',
11822
            api_get_user_id()
11823
        );
11824
11825
        return $item->getId();
11826
    }
11827
11828
    /**
11829
     * @param array $params
11830
     *
11831
     * @throws \Doctrine\ORM\ORMException
11832
     * @throws \Doctrine\ORM\OptimisticLockException
11833
     * @throws \Doctrine\ORM\TransactionRequiredException
11834
     */
11835
    public static function updateCategory($params)
11836
    {
11837
        $em = Database::getManager();
11838
        /** @var CLpCategory $item */
11839
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11840
        if ($item) {
11841
            $item->setName($params['name']);
11842
            $em->merge($item);
11843
            $em->flush();
11844
        }
11845
    }
11846
11847
    /**
11848
     * @param int $id
11849
     *
11850
     * @throws \Doctrine\ORM\ORMException
11851
     * @throws \Doctrine\ORM\OptimisticLockException
11852
     * @throws \Doctrine\ORM\TransactionRequiredException
11853
     */
11854
    public static function moveUpCategory($id)
11855
    {
11856
        $id = (int) $id;
11857
        $em = Database::getManager();
11858
        /** @var CLpCategory $item */
11859
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11860
        if ($item) {
11861
            $position = $item->getPosition() - 1;
11862
            $item->setPosition($position);
11863
            $em->persist($item);
11864
            $em->flush();
11865
        }
11866
    }
11867
11868
    /**
11869
     * @param int $id
11870
     *
11871
     * @throws \Doctrine\ORM\ORMException
11872
     * @throws \Doctrine\ORM\OptimisticLockException
11873
     * @throws \Doctrine\ORM\TransactionRequiredException
11874
     */
11875
    public static function moveDownCategory($id)
11876
    {
11877
        $id = (int) $id;
11878
        $em = Database::getManager();
11879
        /** @var CLpCategory $item */
11880
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11881
        if ($item) {
11882
            $position = $item->getPosition() + 1;
11883
            $item->setPosition($position);
11884
            $em->persist($item);
11885
            $em->flush();
11886
        }
11887
    }
11888
11889
    /**
11890
     * @param int $courseId
11891
     *
11892
     * @throws \Doctrine\ORM\Query\QueryException
11893
     *
11894
     * @return int|mixed
11895
     */
11896
    public static function getCountCategories($courseId)
11897
    {
11898
        if (empty($courseId)) {
11899
            return 0;
11900
        }
11901
        $em = Database::getManager();
11902
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11903
        $query->setParameter('id', $courseId);
11904
11905
        return $query->getSingleScalarResult();
11906
    }
11907
11908
    /**
11909
     * @param int $courseId
11910
     *
11911
     * @return mixed
11912
     */
11913
    public static function getCategories($courseId)
11914
    {
11915
        $em = Database::getManager();
11916
11917
        // Using doctrine extensions
11918
        /** @var SortableRepository $repo */
11919
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11920
        $items = $repo
11921
            ->getBySortableGroupsQuery(['cId' => $courseId])
11922
            ->getResult();
11923
11924
        return $items;
11925
    }
11926
11927
    /**
11928
     * @param int $id
11929
     *
11930
     * @throws \Doctrine\ORM\ORMException
11931
     * @throws \Doctrine\ORM\OptimisticLockException
11932
     * @throws \Doctrine\ORM\TransactionRequiredException
11933
     *
11934
     * @return CLpCategory
11935
     */
11936
    public static function getCategory($id)
11937
    {
11938
        $id = (int) $id;
11939
        $em = Database::getManager();
11940
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11941
11942
        return $item;
11943
    }
11944
11945
    /**
11946
     * @param int $courseId
11947
     *
11948
     * @return array
11949
     */
11950
    public static function getCategoryByCourse($courseId)
11951
    {
11952
        $em = Database::getManager();
11953
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11954
            ['cId' => $courseId]
11955
        );
11956
11957
        return $items;
11958
    }
11959
11960
    /**
11961
     * @param int $id
11962
     *
11963
     * @throws \Doctrine\ORM\ORMException
11964
     * @throws \Doctrine\ORM\OptimisticLockException
11965
     * @throws \Doctrine\ORM\TransactionRequiredException
11966
     *
11967
     * @return mixed
11968
     */
11969
    public static function deleteCategory($id)
11970
    {
11971
        $em = Database::getManager();
11972
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11973
        if ($item) {
11974
            $courseId = $item->getCId();
11975
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11976
            $query->setParameter('id', $courseId);
11977
            $query->setParameter('catId', $item->getId());
11978
            $lps = $query->getResult();
11979
11980
            // Setting category = 0.
11981
            if ($lps) {
11982
                foreach ($lps as $lpItem) {
11983
                    $lpItem->setCategoryId(0);
11984
                }
11985
            }
11986
11987
            // Removing category.
11988
            $em->remove($item);
11989
            $em->flush();
11990
11991
            $courseInfo = api_get_course_info_by_id($courseId);
11992
            $sessionId = api_get_session_id();
11993
11994
            // Delete link tool
11995
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11996
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11997
            // Delete tools
11998
            $sql = "DELETE FROM $tbl_tool
11999
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12000
            Database::query($sql);
12001
12002
            return true;
12003
        }
12004
12005
        return false;
12006
    }
12007
12008
    /**
12009
     * @param int  $courseId
12010
     * @param bool $addSelectOption
12011
     *
12012
     * @return mixed
12013
     */
12014
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12015
    {
12016
        $items = self::getCategoryByCourse($courseId);
12017
        $cats = [];
12018
        if ($addSelectOption) {
12019
            $cats = [get_lang('SelectACategory')];
12020
        }
12021
12022
        if (!empty($items)) {
12023
            foreach ($items as $cat) {
12024
                $cats[$cat->getId()] = $cat->getName();
12025
            }
12026
        }
12027
12028
        return $cats;
12029
    }
12030
12031
    /**
12032
     * @param string $courseCode
12033
     * @param int    $lpId
12034
     * @param int    $user_id
12035
     *
12036
     * @return learnpath
12037
     */
12038
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12039
    {
12040
        $debug = 0;
12041
        $learnPath = null;
12042
        $lpObject = Session::read('lpobject');
12043
        if ($lpObject !== null) {
12044
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12045
            if ($debug) {
12046
                error_log('getLpFromSession: unserialize');
12047
                error_log('------getLpFromSession------');
12048
                error_log('------unserialize------');
12049
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12050
                error_log("api_get_sessionid: ".api_get_session_id());
12051
            }
12052
        }
12053
12054
        if (!is_object($learnPath)) {
12055
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12056
            if ($debug) {
12057
                error_log('------getLpFromSession------');
12058
                error_log('getLpFromSession: create new learnpath');
12059
                error_log("create new LP with $courseCode - $lpId - $user_id");
12060
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12061
                error_log("api_get_sessionid: ".api_get_session_id());
12062
            }
12063
        }
12064
12065
        return $learnPath;
12066
    }
12067
12068
    /**
12069
     * @param int $itemId
12070
     *
12071
     * @return learnpathItem|false
12072
     */
12073
    public function getItem($itemId)
12074
    {
12075
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12076
            return $this->items[$itemId];
12077
        }
12078
12079
        return false;
12080
    }
12081
12082
    /**
12083
     * @return int
12084
     */
12085
    public function getCurrentAttempt()
12086
    {
12087
        $attempt = $this->getItem($this->get_current_item_id());
12088
        if ($attempt) {
12089
            $attemptId = $attempt->get_attempt_id();
12090
12091
            return $attemptId;
12092
        }
12093
12094
        return 0;
12095
    }
12096
12097
    /**
12098
     * @return int
12099
     */
12100
    public function getCategoryId()
12101
    {
12102
        return (int) $this->categoryId;
12103
    }
12104
12105
    /**
12106
     * @param int $categoryId
12107
     *
12108
     * @return bool
12109
     */
12110
    public function setCategoryId($categoryId)
12111
    {
12112
        $this->categoryId = (int) $categoryId;
12113
        $table = Database::get_course_table(TABLE_LP_MAIN);
12114
        $lp_id = $this->get_id();
12115
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12116
                WHERE iid = $lp_id";
12117
        Database::query($sql);
12118
12119
        return true;
12120
    }
12121
12122
    /**
12123
     * Get whether this is a learning path with the possibility to subscribe
12124
     * users or not.
12125
     *
12126
     * @return int
12127
     */
12128
    public function getSubscribeUsers()
12129
    {
12130
        return $this->subscribeUsers;
12131
    }
12132
12133
    /**
12134
     * Set whether this is a learning path with the possibility to subscribe
12135
     * users or not.
12136
     *
12137
     * @param int $value (0 = false, 1 = true)
12138
     *
12139
     * @return bool
12140
     */
12141
    public function setSubscribeUsers($value)
12142
    {
12143
        $this->subscribeUsers = (int) $value;
12144
        $table = Database::get_course_table(TABLE_LP_MAIN);
12145
        $lp_id = $this->get_id();
12146
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12147
                WHERE iid = $lp_id";
12148
        Database::query($sql);
12149
12150
        return true;
12151
    }
12152
12153
    /**
12154
     * Calculate the count of stars for a user in this LP
12155
     * This calculation is based on the following rules:
12156
     * - the student gets one star when he gets to 50% of the learning path
12157
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12158
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12159
     * - the student gets the final star when the score for the *last* test is >= 80%.
12160
     *
12161
     * @param int $sessionId Optional. The session ID
12162
     *
12163
     * @return int The count of stars
12164
     */
12165
    public function getCalculateStars($sessionId = 0)
12166
    {
12167
        $stars = 0;
12168
        $progress = self::getProgress(
12169
            $this->lp_id,
12170
            $this->user_id,
12171
            $this->course_int_id,
12172
            $sessionId
12173
        );
12174
12175
        if ($progress >= 50) {
12176
            $stars++;
12177
        }
12178
12179
        // Calculate stars chapters evaluation
12180
        $exercisesItems = $this->getExercisesItems();
12181
12182
        if (!empty($exercisesItems)) {
12183
            $totalResult = 0;
12184
12185
            foreach ($exercisesItems as $exerciseItem) {
12186
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12187
                    $this->user_id,
12188
                    $exerciseItem->path,
12189
                    $this->course_int_id,
12190
                    $sessionId,
12191
                    $this->lp_id,
12192
                    $exerciseItem->db_id
12193
                );
12194
12195
                $exerciseResultInfo = end($exerciseResultInfo);
12196
12197
                if (!$exerciseResultInfo) {
12198
                    continue;
12199
                }
12200
12201
                if (!empty($exerciseResultInfo['max_score'])) {
12202
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12203
                } else {
12204
                    $exerciseResult = 0;
12205
                }
12206
                $totalResult += $exerciseResult;
12207
            }
12208
12209
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12210
12211
            if ($totalExerciseAverage >= 50) {
12212
                $stars++;
12213
            }
12214
12215
            if ($totalExerciseAverage >= 80) {
12216
                $stars++;
12217
            }
12218
        }
12219
12220
        // Calculate star for final evaluation
12221
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12222
12223
        if (!empty($finalEvaluationItem)) {
12224
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12225
                $this->user_id,
12226
                $finalEvaluationItem->path,
12227
                $this->course_int_id,
12228
                $sessionId,
12229
                $this->lp_id,
12230
                $finalEvaluationItem->db_id
12231
            );
12232
12233
            $evaluationResultInfo = end($evaluationResultInfo);
12234
12235
            if ($evaluationResultInfo) {
12236
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12237
12238
                if ($evaluationResult >= 80) {
12239
                    $stars++;
12240
                }
12241
            }
12242
        }
12243
12244
        return $stars;
12245
    }
12246
12247
    /**
12248
     * Get the items of exercise type.
12249
     *
12250
     * @return array The items. Otherwise return false
12251
     */
12252
    public function getExercisesItems()
12253
    {
12254
        $exercises = [];
12255
        foreach ($this->items as $item) {
12256
            if ($item->type != 'quiz') {
12257
                continue;
12258
            }
12259
            $exercises[] = $item;
12260
        }
12261
12262
        array_pop($exercises);
12263
12264
        return $exercises;
12265
    }
12266
12267
    /**
12268
     * Get the item of exercise type (evaluation type).
12269
     *
12270
     * @return array The final evaluation. Otherwise return false
12271
     */
12272
    public function getFinalEvaluationItem()
12273
    {
12274
        $exercises = [];
12275
        foreach ($this->items as $item) {
12276
            if ($item->type != 'quiz') {
12277
                continue;
12278
            }
12279
12280
            $exercises[] = $item;
12281
        }
12282
12283
        return array_pop($exercises);
12284
    }
12285
12286
    /**
12287
     * Calculate the total points achieved for the current user in this learning path.
12288
     *
12289
     * @param int $sessionId Optional. The session Id
12290
     *
12291
     * @return int
12292
     */
12293
    public function getCalculateScore($sessionId = 0)
12294
    {
12295
        // Calculate stars chapters evaluation
12296
        $exercisesItems = $this->getExercisesItems();
12297
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12298
        $totalExercisesResult = 0;
12299
        $totalEvaluationResult = 0;
12300
12301
        if ($exercisesItems !== false) {
12302
            foreach ($exercisesItems as $exerciseItem) {
12303
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12304
                    $this->user_id,
12305
                    $exerciseItem->path,
12306
                    $this->course_int_id,
12307
                    $sessionId,
12308
                    $this->lp_id,
12309
                    $exerciseItem->db_id
12310
                );
12311
12312
                $exerciseResultInfo = end($exerciseResultInfo);
12313
12314
                if (!$exerciseResultInfo) {
12315
                    continue;
12316
                }
12317
12318
                $totalExercisesResult += $exerciseResultInfo['score'];
12319
            }
12320
        }
12321
12322
        if (!empty($finalEvaluationItem)) {
12323
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12324
                $this->user_id,
12325
                $finalEvaluationItem->path,
12326
                $this->course_int_id,
12327
                $sessionId,
12328
                $this->lp_id,
12329
                $finalEvaluationItem->db_id
12330
            );
12331
12332
            $evaluationResultInfo = end($evaluationResultInfo);
12333
12334
            if ($evaluationResultInfo) {
12335
                $totalEvaluationResult += $evaluationResultInfo['score'];
12336
            }
12337
        }
12338
12339
        return $totalExercisesResult + $totalEvaluationResult;
12340
    }
12341
12342
    /**
12343
     * Check if URL is not allowed to be show in a iframe.
12344
     *
12345
     * @param string $src
12346
     *
12347
     * @return string
12348
     */
12349
    public function fixBlockedLinks($src)
12350
    {
12351
        $urlInfo = parse_url($src);
12352
12353
        $platformProtocol = 'https';
12354
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12355
            $platformProtocol = 'http';
12356
        }
12357
12358
        $protocolFixApplied = false;
12359
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12360
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12361
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12362
12363
        if ($platformProtocol != $scheme) {
12364
            Session::write('x_frame_source', $src);
12365
            $src = 'blank.php?error=x_frames_options';
12366
            $protocolFixApplied = true;
12367
        }
12368
12369
        if ($protocolFixApplied == false) {
12370
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12371
                // Check X-Frame-Options
12372
                $ch = curl_init();
12373
                $options = [
12374
                    CURLOPT_URL => $src,
12375
                    CURLOPT_RETURNTRANSFER => true,
12376
                    CURLOPT_HEADER => true,
12377
                    CURLOPT_FOLLOWLOCATION => true,
12378
                    CURLOPT_ENCODING => "",
12379
                    CURLOPT_AUTOREFERER => true,
12380
                    CURLOPT_CONNECTTIMEOUT => 120,
12381
                    CURLOPT_TIMEOUT => 120,
12382
                    CURLOPT_MAXREDIRS => 10,
12383
                ];
12384
12385
                $proxySettings = api_get_configuration_value('proxy_settings');
12386
                if (!empty($proxySettings) &&
12387
                    isset($proxySettings['curl_setopt_array'])
12388
                ) {
12389
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12390
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12391
                }
12392
12393
                curl_setopt_array($ch, $options);
12394
                $response = curl_exec($ch);
12395
                $httpCode = curl_getinfo($ch);
12396
                $headers = substr($response, 0, $httpCode['header_size']);
12397
12398
                $error = false;
12399
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12400
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12401
                ) {
12402
                    $error = true;
12403
                }
12404
12405
                if ($error) {
12406
                    Session::write('x_frame_source', $src);
12407
                    $src = 'blank.php?error=x_frames_options';
12408
                }
12409
            }
12410
        }
12411
12412
        return $src;
12413
    }
12414
12415
    /**
12416
     * Check if this LP has a created forum in the basis course.
12417
     *
12418
     * @return bool
12419
     */
12420
    public function lpHasForum()
12421
    {
12422
        $forumTable = Database::get_course_table(TABLE_FORUM);
12423
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12424
12425
        $fakeFrom = "
12426
            $forumTable f
12427
            INNER JOIN $itemProperty ip
12428
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12429
        ";
12430
12431
        $resultData = Database::select(
12432
            'COUNT(f.iid) AS qty',
12433
            $fakeFrom,
12434
            [
12435
                'where' => [
12436
                    'ip.visibility != ? AND ' => 2,
12437
                    'ip.tool = ? AND ' => TOOL_FORUM,
12438
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12439
                    'f.lp_id = ?' => intval($this->lp_id),
12440
                ],
12441
            ],
12442
            'first'
12443
        );
12444
12445
        return $resultData['qty'] > 0;
12446
    }
12447
12448
    /**
12449
     * Get the forum for this learning path.
12450
     *
12451
     * @param int $sessionId
12452
     *
12453
     * @return bool
12454
     */
12455
    public function getForum($sessionId = 0)
12456
    {
12457
        $forumTable = Database::get_course_table(TABLE_FORUM);
12458
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12459
12460
        $fakeFrom = "$forumTable f
12461
            INNER JOIN $itemProperty ip ";
12462
12463
        if ($this->lp_session_id == 0) {
12464
            $fakeFrom .= "
12465
                ON (
12466
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12467
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12468
                    )
12469
                )
12470
            ";
12471
        } else {
12472
            $fakeFrom .= "
12473
                ON (
12474
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12475
                )
12476
            ";
12477
        }
12478
12479
        $resultData = Database::select(
12480
            'f.*',
12481
            $fakeFrom,
12482
            [
12483
                'where' => [
12484
                    'ip.visibility != ? AND ' => 2,
12485
                    'ip.tool = ? AND ' => TOOL_FORUM,
12486
                    'f.session_id = ? AND ' => $sessionId,
12487
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12488
                    'f.lp_id = ?' => intval($this->lp_id),
12489
                ],
12490
            ],
12491
            'first'
12492
        );
12493
12494
        if (empty($resultData)) {
12495
            return false;
12496
        }
12497
12498
        return $resultData;
12499
    }
12500
12501
    /**
12502
     * Create a forum for this learning path.
12503
     *
12504
     * @param int $forumCategoryId
12505
     *
12506
     * @return int The forum ID if was created. Otherwise return false
12507
     */
12508
    public function createForum($forumCategoryId)
12509
    {
12510
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12511
12512
        $forumId = store_forum(
12513
            [
12514
                'lp_id' => $this->lp_id,
12515
                'forum_title' => $this->name,
12516
                'forum_comment' => null,
12517
                'forum_category' => (int) $forumCategoryId,
12518
                'students_can_edit_group' => ['students_can_edit' => 0],
12519
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12520
                'default_view_type_group' => ['default_view_type' => 'flat'],
12521
                'group_forum' => 0,
12522
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12523
            ],
12524
            [],
12525
            true
12526
        );
12527
12528
        return $forumId;
12529
    }
12530
12531
    /**
12532
     * Get the LP Final Item form.
12533
     *
12534
     * @throws Exception
12535
     * @throws HTML_QuickForm_Error
12536
     *
12537
     * @return string
12538
     */
12539
    public function getFinalItemForm()
12540
    {
12541
        $finalItem = $this->getFinalItem();
12542
        $title = '';
12543
12544
        if ($finalItem) {
12545
            $title = $finalItem->get_title();
12546
            $buttonText = get_lang('Save');
12547
            $content = $this->getSavedFinalItem();
12548
        } else {
12549
            $buttonText = get_lang('LPCreateDocument');
12550
            $content = $this->getFinalItemTemplate();
12551
        }
12552
12553
        $courseInfo = api_get_course_info();
12554
        $result = $this->generate_lp_folder($courseInfo);
12555
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12556
        $relative_prefix = '../../';
12557
12558
        $editorConfig = [
12559
            'ToolbarSet' => 'LearningPathDocuments',
12560
            'Width' => '100%',
12561
            'Height' => '500',
12562
            'FullPage' => true,
12563
            'CreateDocumentDir' => $relative_prefix,
12564
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12565
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12566
        ];
12567
12568
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12569
            'type' => 'document',
12570
            'lp_id' => $this->lp_id,
12571
        ]);
12572
12573
        $form = new FormValidator('final_item', 'POST', $url);
12574
        $form->addText('title', get_lang('Title'));
12575
        $form->addButtonSave($buttonText);
12576
        $form->addHtml(
12577
            Display::return_message(
12578
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12579
                'normal',
12580
                false
12581
            )
12582
        );
12583
12584
        $renderer = $form->defaultRenderer();
12585
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12586
12587
        $form->addHtmlEditor(
12588
            'content_lp_certificate',
12589
            null,
12590
            true,
12591
            false,
12592
            $editorConfig,
12593
            true
12594
        );
12595
        $form->addHidden('action', 'add_final_item');
12596
        $form->addHidden('path', Session::read('pathItem'));
12597
        $form->addHidden('previous', $this->get_last());
12598
        $form->setDefaults(
12599
            ['title' => $title, 'content_lp_certificate' => $content]
12600
        );
12601
12602
        if ($form->validate()) {
12603
            $values = $form->exportValues();
12604
            $lastItemId = $this->getLastInFirstLevel();
12605
12606
            if (!$finalItem) {
12607
                $documentId = $this->create_document(
12608
                    $this->course_info,
12609
                    $values['content_lp_certificate'],
12610
                    $values['title']
12611
                );
12612
                $this->add_item(
12613
                    0,
12614
                    $lastItemId,
12615
                    'final_item',
12616
                    $documentId,
12617
                    $values['title'],
12618
                    ''
12619
                );
12620
12621
                Display::addFlash(
12622
                    Display::return_message(get_lang('Added'))
12623
                );
12624
            } else {
12625
                $this->edit_document($this->course_info);
12626
            }
12627
        }
12628
12629
        return $form->returnForm();
12630
    }
12631
12632
    /**
12633
     * Check if the current lp item is first, both, last or none from lp list.
12634
     *
12635
     * @param int $currentItemId
12636
     *
12637
     * @return string
12638
     */
12639
    public function isFirstOrLastItem($currentItemId)
12640
    {
12641
        $lpItemId = [];
12642
        $typeListNotToVerify = self::getChapterTypes();
12643
12644
        // Using get_toc() function instead $this->items because returns the correct order of the items
12645
        foreach ($this->get_toc() as $item) {
12646
            if (!in_array($item['type'], $typeListNotToVerify)) {
12647
                $lpItemId[] = $item['id'];
12648
            }
12649
        }
12650
12651
        $lastLpItemIndex = count($lpItemId) - 1;
12652
        $position = array_search($currentItemId, $lpItemId);
12653
12654
        switch ($position) {
12655
            case 0:
12656
                if (!$lastLpItemIndex) {
12657
                    $answer = 'both';
12658
                    break;
12659
                }
12660
12661
                $answer = 'first';
12662
                break;
12663
            case $lastLpItemIndex:
12664
                $answer = 'last';
12665
                break;
12666
            default:
12667
                $answer = 'none';
12668
        }
12669
12670
        return $answer;
12671
    }
12672
12673
    /**
12674
     * Get whether this is a learning path with the accumulated SCORM time or not.
12675
     *
12676
     * @return int
12677
     */
12678
    public function getAccumulateScormTime()
12679
    {
12680
        return $this->accumulateScormTime;
12681
    }
12682
12683
    /**
12684
     * Set whether this is a learning path with the accumulated SCORM time or not.
12685
     *
12686
     * @param int $value (0 = false, 1 = true)
12687
     *
12688
     * @return bool Always returns true
12689
     */
12690
    public function setAccumulateScormTime($value)
12691
    {
12692
        $this->accumulateScormTime = (int) $value;
12693
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12694
        $lp_id = $this->get_id();
12695
        $sql = "UPDATE $lp_table 
12696
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12697
                WHERE iid = $lp_id";
12698
        Database::query($sql);
12699
12700
        return true;
12701
    }
12702
12703
    /**
12704
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12705
     * the new learning path tool.
12706
     *
12707
     * The function is a big switch on tool type.
12708
     * In each case, we query the corresponding table for information and build the link
12709
     * with that information.
12710
     *
12711
     * @author Yannick Warnier <[email protected]> - rebranding based on
12712
     * previous work (display_addedresource_link_in_learnpath())
12713
     *
12714
     * @param int $course_id      Course code
12715
     * @param int $learningPathId The learning path ID (in lp table)
12716
     * @param int $id_in_path     the unique index in the items table
12717
     * @param int $lpViewId
12718
     *
12719
     * @return string
12720
     */
12721
    public static function rl_get_resource_link_for_learnpath(
12722
        $course_id,
12723
        $learningPathId,
12724
        $id_in_path,
12725
        $lpViewId
12726
    ) {
12727
        $session_id = api_get_session_id();
12728
        $course_info = api_get_course_info_by_id($course_id);
12729
12730
        $learningPathId = (int) $learningPathId;
12731
        $id_in_path = (int) $id_in_path;
12732
        $lpViewId = (int) $lpViewId;
12733
12734
        $em = Database::getManager();
12735
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12736
12737
        /** @var CLpItem $rowItem */
12738
        $rowItem = $lpItemRepo->findOneBy([
12739
            'cId' => $course_id,
12740
            'lpId' => $learningPathId,
12741
            'iid' => $id_in_path,
12742
        ]);
12743
12744
        if (!$rowItem) {
12745
            // Try one more time with "id"
12746
            /** @var CLpItem $rowItem */
12747
            $rowItem = $lpItemRepo->findOneBy([
12748
                'cId' => $course_id,
12749
                'lpId' => $learningPathId,
12750
                'id' => $id_in_path,
12751
            ]);
12752
12753
            if (!$rowItem) {
12754
                return -1;
12755
            }
12756
        }
12757
12758
        $course_code = $course_info['code'];
12759
        $type = $rowItem->getItemType();
12760
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12761
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12762
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12763
        $link = '';
12764
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12765
12766
        switch ($type) {
12767
            case 'dir':
12768
                return $main_dir_path.'lp/blank.php';
12769
            case TOOL_CALENDAR_EVENT:
12770
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12771
            case TOOL_ANNOUNCEMENT:
12772
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12773
            case TOOL_LINK:
12774
                $linkInfo = Link::getLinkInfo($id);
12775
                if (isset($linkInfo['url'])) {
12776
                    return $linkInfo['url'];
12777
                }
12778
12779
                return '';
12780
            case TOOL_QUIZ:
12781
                if (empty($id)) {
12782
                    return '';
12783
                }
12784
12785
                // Get the lp_item_view with the highest view_count.
12786
                $learnpathItemViewResult = $em
12787
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12788
                    ->findBy(
12789
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12790
                        ['viewCount' => 'DESC'],
12791
                        1
12792
                    );
12793
                /** @var CLpItemView $learnpathItemViewData */
12794
                $learnpathItemViewData = current($learnpathItemViewResult);
12795
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12796
12797
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12798
                    .http_build_query([
12799
                        'lp_init' => 1,
12800
                        'learnpath_item_view_id' => $learnpathItemViewId,
12801
                        'learnpath_id' => $learningPathId,
12802
                        'learnpath_item_id' => $id_in_path,
12803
                        'exerciseId' => $id,
12804
                    ]);
12805
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12806
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12807
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12808
                $myrow = Database::fetch_array($result);
12809
                $path = $myrow['path'];
12810
12811
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12812
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12813
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12814
            case TOOL_FORUM:
12815
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12816
            case TOOL_THREAD:
12817
                // forum post
12818
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12819
                if (empty($id)) {
12820
                    return '';
12821
                }
12822
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12823
                $result = Database::query($sql);
12824
                $myrow = Database::fetch_array($result);
12825
12826
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12827
                    .$extraParams;
12828
            case TOOL_POST:
12829
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12830
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12831
                $myrow = Database::fetch_array($result);
12832
12833
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12834
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12835
            case TOOL_READOUT_TEXT:
12836
                return api_get_path(WEB_CODE_PATH).
12837
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12838
            case TOOL_DOCUMENT:
12839
                $document = $em
12840
                    ->getRepository('ChamiloCourseBundle:CDocument')
12841
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
12842
12843
                if (empty($document)) {
12844
                    // Try with normal id
12845
                    $document = $em
12846
                        ->getRepository('ChamiloCourseBundle:CDocument')
12847
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
12848
12849
                    if (empty($document)) {
12850
                        return '';
12851
                    }
12852
                }
12853
12854
                $documentPathInfo = pathinfo($document->getPath());
12855
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
12856
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12857
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12858
12859
                $openmethod = 2;
12860
                $officedoc = false;
12861
                Session::write('openmethod', $openmethod);
12862
                Session::write('officedoc', $officedoc);
12863
12864
                if ($showDirectUrl) {
12865
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12866
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12867
                        if (Link::isPdfLink($file)) {
12868
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12869
12870
                            return $pdfUrl;
12871
                        }
12872
                    }
12873
12874
                    return $file;
12875
                }
12876
12877
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12878
            case TOOL_LP_FINAL_ITEM:
12879
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12880
                    .$extraParams;
12881
            case 'assignments':
12882
                return $main_dir_path.'work/work.php?'.$extraParams;
12883
            case TOOL_DROPBOX:
12884
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12885
            case 'introduction_text': //DEPRECATED
12886
                return '';
12887
            case TOOL_COURSE_DESCRIPTION:
12888
                return $main_dir_path.'course_description?'.$extraParams;
12889
            case TOOL_GROUP:
12890
                return $main_dir_path.'group/group.php?'.$extraParams;
12891
            case TOOL_USER:
12892
                return $main_dir_path.'user/user.php?'.$extraParams;
12893
            case TOOL_STUDENTPUBLICATION:
12894
                if (!empty($rowItem->getPath())) {
12895
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12896
                }
12897
12898
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12899
        }
12900
12901
        return $link;
12902
    }
12903
12904
    /**
12905
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12906
     *
12907
     * @author Yannick Warnier <[email protected]>
12908
     *
12909
     * @param string $course_code    Course code
12910
     * @param int    $learningPathId
12911
     * @param int    $id_in_path     The resource ID
12912
     *
12913
     * @return string
12914
     */
12915
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12916
    {
12917
        $_course = api_get_course_info($course_code);
12918
        if (empty($_course)) {
12919
            return '';
12920
        }
12921
        $course_id = $_course['real_id'];
12922
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12923
        $learningPathId = (int) $learningPathId;
12924
        $id_in_path = (int) $id_in_path;
12925
12926
        $sql = "SELECT item_type, title, ref 
12927
                FROM $tbl_lp_item
12928
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12929
        $res_item = Database::query($sql);
12930
12931
        if (Database::num_rows($res_item) < 1) {
12932
            return '';
12933
        }
12934
        $row_item = Database::fetch_array($res_item);
12935
        $type = strtolower($row_item['item_type']);
12936
        $id = $row_item['ref'];
12937
        $output = '';
12938
12939
        switch ($type) {
12940
            case TOOL_CALENDAR_EVENT:
12941
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12942
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12943
                $myrow = Database::fetch_array($result);
12944
                $output = $myrow['title'];
12945
                break;
12946
            case TOOL_ANNOUNCEMENT:
12947
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12948
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12949
                $myrow = Database::fetch_array($result);
12950
                $output = $myrow['title'];
12951
                break;
12952
            case TOOL_LINK:
12953
                // Doesn't take $target into account.
12954
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12955
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12956
                $myrow = Database::fetch_array($result);
12957
                $output = $myrow['title'];
12958
                break;
12959
            case TOOL_QUIZ:
12960
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12961
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12962
                $myrow = Database::fetch_array($result);
12963
                $output = $myrow['title'];
12964
                break;
12965
            case TOOL_FORUM:
12966
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12967
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12968
                $myrow = Database::fetch_array($result);
12969
                $output = $myrow['forum_name'];
12970
                break;
12971
            case TOOL_THREAD:
12972
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12973
                // Grabbing the title of the post.
12974
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12975
                $result_title = Database::query($sql_title);
12976
                $myrow_title = Database::fetch_array($result_title);
12977
                $output = $myrow_title['post_title'];
12978
                break;
12979
            case TOOL_POST:
12980
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12981
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12982
                $result = Database::query($sql);
12983
                $post = Database::fetch_array($result);
12984
                $output = $post['post_title'];
12985
                break;
12986
            case 'dir':
12987
            case TOOL_DOCUMENT:
12988
                $title = $row_item['title'];
12989
                $output = '-';
12990
                if (!empty($title)) {
12991
                    $output = $title;
12992
                }
12993
                break;
12994
            case 'hotpotatoes':
12995
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12996
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12997
                $myrow = Database::fetch_array($result);
12998
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12999
                $last = count($pathname) - 1; // Making a correct name for the link.
13000
                $filename = $pathname[$last]; // Making a correct name for the link.
13001
                $myrow['path'] = rawurlencode($myrow['path']);
13002
                $output = $filename;
13003
                break;
13004
        }
13005
13006
        return stripslashes($output);
13007
    }
13008
13009
    /**
13010
     * Get the parent names for the current item.
13011
     *
13012
     * @param int $newItemId Optional. The item ID
13013
     *
13014
     * @return array
13015
     */
13016
    public function getCurrentItemParentNames($newItemId = 0)
13017
    {
13018
        $newItemId = $newItemId ?: $this->get_current_item_id();
13019
        $return = [];
13020
        $item = $this->getItem($newItemId);
13021
        $parent = $this->getItem($item->get_parent());
13022
13023
        while ($parent) {
13024
            $return[] = $parent->get_title();
13025
            $parent = $this->getItem($parent->get_parent());
13026
        }
13027
13028
        return array_reverse($return);
13029
    }
13030
13031
    /**
13032
     * Reads and process "lp_subscription_settings" setting.
13033
     *
13034
     * @return array
13035
     */
13036
    public static function getSubscriptionSettings()
13037
    {
13038
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13039
        if (empty($subscriptionSettings)) {
13040
            // By default allow both settings
13041
            $subscriptionSettings = [
13042
                'allow_add_users_to_lp' => true,
13043
                'allow_add_users_to_lp_category' => true,
13044
            ];
13045
        } else {
13046
            $subscriptionSettings = $subscriptionSettings['options'];
13047
        }
13048
13049
        return $subscriptionSettings;
13050
    }
13051
13052
    /**
13053
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13054
     */
13055
    public function exportToCourseBuildFormat()
13056
    {
13057
        if (!api_is_allowed_to_edit()) {
13058
            return false;
13059
        }
13060
13061
        $courseBuilder = new CourseBuilder();
13062
        $itemList = [];
13063
        /** @var learnpathItem $item */
13064
        foreach ($this->items as $item) {
13065
            $itemList[$item->get_type()][] = $item->get_path();
13066
        }
13067
13068
        if (empty($itemList)) {
13069
            return false;
13070
        }
13071
13072
        if (isset($itemList['document'])) {
13073
            // Get parents
13074
            foreach ($itemList['document'] as $documentId) {
13075
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13076
                if (!empty($documentInfo['parents'])) {
13077
                    foreach ($documentInfo['parents'] as $parentInfo) {
13078
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13079
                            continue;
13080
                        }
13081
                        $itemList['document'][] = $parentInfo['iid'];
13082
                    }
13083
                }
13084
            }
13085
13086
            $courseInfo = api_get_course_info();
13087
            foreach ($itemList['document'] as $documentId) {
13088
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13089
                $items = DocumentManager::get_resources_from_source_html(
13090
                    $documentInfo['absolute_path'],
13091
                    true,
13092
                    TOOL_DOCUMENT
13093
                );
13094
13095
                if (!empty($items)) {
13096
                    foreach ($items as $item) {
13097
                        // Get information about source url
13098
                        $url = $item[0]; // url
13099
                        $scope = $item[1]; // scope (local, remote)
13100
                        $type = $item[2]; // type (rel, abs, url)
13101
13102
                        $origParseUrl = parse_url($url);
13103
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13104
13105
                        if ($scope == 'local') {
13106
                            if ($type == 'abs' || $type == 'rel') {
13107
                                $documentFile = strstr($realOrigPath, 'document');
13108
                                if (strpos($realOrigPath, $documentFile) !== false) {
13109
                                    $documentFile = str_replace('document', '', $documentFile);
13110
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13111
                                    // Document found! Add it to the list
13112
                                    if ($itemDocumentId) {
13113
                                        $itemList['document'][] = $itemDocumentId;
13114
                                    }
13115
                                }
13116
                            }
13117
                        }
13118
                    }
13119
                }
13120
            }
13121
13122
            $courseBuilder->build_documents(
13123
                api_get_session_id(),
13124
                $this->get_course_int_id(),
13125
                true,
13126
                $itemList['document']
13127
            );
13128
        }
13129
13130
        if (isset($itemList['quiz'])) {
13131
            $courseBuilder->build_quizzes(
13132
                api_get_session_id(),
13133
                $this->get_course_int_id(),
13134
                true,
13135
                $itemList['quiz']
13136
            );
13137
        }
13138
13139
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13140
13141
        /*if (!empty($itemList['thread'])) {
13142
            $postList = [];
13143
            foreach ($itemList['thread'] as $postId) {
13144
                $post = get_post_information($postId);
13145
                if ($post) {
13146
                    if (!isset($itemList['forum'])) {
13147
                        $itemList['forum'] = [];
13148
                    }
13149
                    $itemList['forum'][] = $post['forum_id'];
13150
                    $postList[] = $postId;
13151
                }
13152
            }
13153
13154
            if (!empty($postList)) {
13155
                $courseBuilder->build_forum_posts(
13156
                    $this->get_course_int_id(),
13157
                    null,
13158
                    null,
13159
                    $postList
13160
                );
13161
            }
13162
        }*/
13163
13164
        if (!empty($itemList['thread'])) {
13165
            $threadList = [];
13166
            $em = Database::getManager();
13167
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13168
            foreach ($itemList['thread'] as $threadId) {
13169
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13170
                $thread = $repo->find($threadId);
13171
                if ($thread) {
13172
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13173
                    $threadList[] = $thread->getIid();
13174
                }
13175
            }
13176
13177
            if (!empty($threadList)) {
13178
                $courseBuilder->build_forum_topics(
13179
                    api_get_session_id(),
13180
                    $this->get_course_int_id(),
13181
                    null,
13182
                    $threadList
13183
                );
13184
            }
13185
        }
13186
13187
        $forumCategoryList = [];
13188
        if (isset($itemList['forum'])) {
13189
            foreach ($itemList['forum'] as $forumId) {
13190
                $forumInfo = get_forums($forumId);
13191
                $forumCategoryList[] = $forumInfo['forum_category'];
13192
            }
13193
        }
13194
13195
        if (!empty($forumCategoryList)) {
13196
            $courseBuilder->build_forum_category(
13197
                api_get_session_id(),
13198
                $this->get_course_int_id(),
13199
                true,
13200
                $forumCategoryList
13201
            );
13202
        }
13203
13204
        if (!empty($itemList['forum'])) {
13205
            $courseBuilder->build_forums(
13206
                api_get_session_id(),
13207
                $this->get_course_int_id(),
13208
                true,
13209
                $itemList['forum']
13210
            );
13211
        }
13212
13213
        if (isset($itemList['link'])) {
13214
            $courseBuilder->build_links(
13215
                api_get_session_id(),
13216
                $this->get_course_int_id(),
13217
                true,
13218
                $itemList['link']
13219
            );
13220
        }
13221
13222
        if (!empty($itemList['student_publication'])) {
13223
            $courseBuilder->build_works(
13224
                api_get_session_id(),
13225
                $this->get_course_int_id(),
13226
                true,
13227
                $itemList['student_publication']
13228
            );
13229
        }
13230
13231
        $courseBuilder->build_learnpaths(
13232
            api_get_session_id(),
13233
            $this->get_course_int_id(),
13234
            true,
13235
            [$this->get_id()],
13236
            false
13237
        );
13238
13239
        $courseBuilder->restoreDocumentsFromList();
13240
13241
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13242
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13243
        $result = DocumentManager::file_send_for_download(
13244
            $zipPath,
13245
            true,
13246
            $this->get_name().'.zip'
13247
        );
13248
13249
        if ($result) {
13250
            api_not_allowed();
13251
        }
13252
13253
        return true;
13254
    }
13255
13256
    /**
13257
     * Get whether this is a learning path with the accumulated work time or not.
13258
     *
13259
     * @return int
13260
     */
13261
    public function getAccumulateWorkTime()
13262
    {
13263
        return (int) $this->accumulateWorkTime;
13264
    }
13265
13266
    /**
13267
     * Get whether this is a learning path with the accumulated work time or not.
13268
     *
13269
     * @return int
13270
     */
13271
    public function getAccumulateWorkTimeTotalCourse()
13272
    {
13273
        $table = Database::get_course_table(TABLE_LP_MAIN);
13274
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13275
                FROM $table 
13276
                WHERE c_id = ".$this->course_int_id;
13277
        $result = Database::query($sql);
13278
        $row = Database::fetch_array($result);
13279
13280
        return (int) $row['total'];
13281
    }
13282
13283
    /**
13284
     * Set whether this is a learning path with the accumulated work time or not.
13285
     *
13286
     * @param int $value (0 = false, 1 = true)
13287
     *
13288
     * @return bool
13289
     */
13290
    public function setAccumulateWorkTime($value)
13291
    {
13292
        if (!api_get_configuration_value('lp_minimum_time')) {
13293
            return false;
13294
        }
13295
13296
        $this->accumulateWorkTime = (int) $value;
13297
        $table = Database::get_course_table(TABLE_LP_MAIN);
13298
        $lp_id = $this->get_id();
13299
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13300
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13301
        Database::query($sql);
13302
13303
        return true;
13304
    }
13305
13306
    /**
13307
     * @param int $lpId
13308
     * @param int $courseId
13309
     *
13310
     * @return mixed
13311
     */
13312
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13313
    {
13314
        $lpId = (int) $lpId;
13315
        $courseId = (int) $courseId;
13316
13317
        $table = Database::get_course_table(TABLE_LP_MAIN);
13318
        $sql = "SELECT accumulate_work_time 
13319
                FROM $table 
13320
                WHERE c_id = $courseId AND id = $lpId";
13321
        $result = Database::query($sql);
13322
        $row = Database::fetch_array($result);
13323
13324
        return $row['accumulate_work_time'];
13325
    }
13326
13327
    /**
13328
     * @param int $courseId
13329
     *
13330
     * @return int
13331
     */
13332
    public static function getAccumulateWorkTimeTotal($courseId)
13333
    {
13334
        $table = Database::get_course_table(TABLE_LP_MAIN);
13335
        $courseId = (int) $courseId;
13336
        $sql = "SELECT SUM(accumulate_work_time) AS total
13337
                FROM $table
13338
                WHERE c_id = $courseId";
13339
        $result = Database::query($sql);
13340
        $row = Database::fetch_array($result);
13341
13342
        return (int) $row['total'];
13343
    }
13344
13345
    /**
13346
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13347
     * and put the images in.
13348
     *
13349
     * @return array
13350
     */
13351
    public static function getIconSelect()
13352
    {
13353
        $theme = api_get_visual_theme();
13354
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13355
        $icons = ['' => get_lang('SelectAnOption')];
13356
13357
        if (is_dir($path)) {
13358
            $finder = new Finder();
13359
            $finder->files()->in($path);
13360
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13361
            /** @var SplFileInfo $file */
13362
            foreach ($finder as $file) {
13363
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13364
                    $icons[$file->getFilename()] = $file->getFilename();
13365
                }
13366
            }
13367
        }
13368
13369
        return $icons;
13370
    }
13371
13372
    /**
13373
     * @param int $lpId
13374
     *
13375
     * @return string
13376
     */
13377
    public static function getSelectedIcon($lpId)
13378
    {
13379
        $extraFieldValue = new ExtraFieldValue('lp');
13380
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13381
        $icon = '';
13382
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13383
            $icon = $lpIcon['value'];
13384
        }
13385
13386
        return $icon;
13387
    }
13388
13389
    /**
13390
     * @param int $lpId
13391
     *
13392
     * @return string
13393
     */
13394
    public static function getSelectedIconHtml($lpId)
13395
    {
13396
        $icon = self::getSelectedIcon($lpId);
13397
13398
        if (empty($icon)) {
13399
            return '';
13400
        }
13401
13402
        $theme = api_get_visual_theme();
13403
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13404
13405
        return Display::img($path);
13406
    }
13407
13408
    /**
13409
     * @param string $value
13410
     *
13411
     * @return string
13412
     */
13413
    public function cleanItemTitle($value)
13414
    {
13415
        $value = Security::remove_XSS(strip_tags($value));
13416
13417
        return $value;
13418
    }
13419
13420
    /**
13421
     * @param FormValidator $form
13422
     */
13423
    public function setItemTitle(FormValidator $form)
13424
    {
13425
        if (api_get_configuration_value('save_titles_as_html')) {
13426
            $form->addHtmlEditor(
13427
                'title',
13428
                get_lang('Title'),
13429
                true,
13430
                false,
13431
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13432
            );
13433
        } else {
13434
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13435
            $form->applyFilter('title', 'trim');
13436
            $form->applyFilter('title', 'html_filter');
13437
        }
13438
    }
13439
13440
    /**
13441
     * @return array
13442
     */
13443
    public function getItemsForForm($addParentCondition = false)
13444
    {
13445
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13446
        $course_id = api_get_course_int_id();
13447
13448
        $sql = "SELECT * FROM $tbl_lp_item 
13449
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13450
13451
        if ($addParentCondition) {
13452
            $sql .= ' AND parent_item_id = 0 ';
13453
        }
13454
        $sql .= ' ORDER BY display_order ASC';
13455
13456
        $result = Database::query($sql);
13457
        $arrLP = [];
13458
        while ($row = Database::fetch_array($result)) {
13459
            $arrLP[] = [
13460
                'iid' => $row['iid'],
13461
                'id' => $row['iid'],
13462
                'item_type' => $row['item_type'],
13463
                'title' => $this->cleanItemTitle($row['title']),
13464
                'title_raw' => $row['title'],
13465
                'path' => $row['path'],
13466
                'description' => Security::remove_XSS($row['description']),
13467
                'parent_item_id' => $row['parent_item_id'],
13468
                'previous_item_id' => $row['previous_item_id'],
13469
                'next_item_id' => $row['next_item_id'],
13470
                'display_order' => $row['display_order'],
13471
                'max_score' => $row['max_score'],
13472
                'min_score' => $row['min_score'],
13473
                'mastery_score' => $row['mastery_score'],
13474
                'prerequisite' => $row['prerequisite'],
13475
                'max_time_allowed' => $row['max_time_allowed'],
13476
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13477
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13478
            ];
13479
        }
13480
13481
        return $arrLP;
13482
    }
13483
13484
    /**
13485
     * Get the depth level of LP item.
13486
     *
13487
     * @param array $items
13488
     * @param int   $currentItemId
13489
     *
13490
     * @return int
13491
     */
13492
    private static function get_level_for_item($items, $currentItemId)
13493
    {
13494
        $parentItemId = 0;
13495
        if (isset($items[$currentItemId])) {
13496
            $parentItemId = $items[$currentItemId]->parent;
13497
        }
13498
13499
        if ($parentItemId == 0) {
13500
            return 0;
13501
        } else {
13502
            return self::get_level_for_item($items, $parentItemId) + 1;
13503
        }
13504
    }
13505
13506
    /**
13507
     * Generate the link for a learnpath category as course tool.
13508
     *
13509
     * @param int $categoryId
13510
     *
13511
     * @return string
13512
     */
13513
    private static function getCategoryLinkForTool($categoryId)
13514
    {
13515
        $categoryId = (int) $categoryId;
13516
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13517
            .http_build_query(
13518
                [
13519
                    'action' => 'view_category',
13520
                    'id' => $categoryId,
13521
                ]
13522
            );
13523
13524
        return $link;
13525
    }
13526
13527
    /**
13528
     * Return the scorm item type object with spaces replaced with _
13529
     * The return result is use to build a css classname like scorm_type_$return.
13530
     *
13531
     * @param $in_type
13532
     *
13533
     * @return mixed
13534
     */
13535
    private static function format_scorm_type_item($in_type)
13536
    {
13537
        return str_replace(' ', '_', $in_type);
13538
    }
13539
13540
    /**
13541
     * Check and obtain the lp final item if exist.
13542
     *
13543
     * @return learnpathItem
13544
     */
13545
    private function getFinalItem()
13546
    {
13547
        if (empty($this->items)) {
13548
            return null;
13549
        }
13550
13551
        foreach ($this->items as $item) {
13552
            if ($item->type !== 'final_item') {
13553
                continue;
13554
            }
13555
13556
            return $item;
13557
        }
13558
    }
13559
13560
    /**
13561
     * Get the LP Final Item Template.
13562
     *
13563
     * @return string
13564
     */
13565
    private function getFinalItemTemplate()
13566
    {
13567
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13568
    }
13569
13570
    /**
13571
     * Get the LP Final Item Url.
13572
     *
13573
     * @return string
13574
     */
13575
    private function getSavedFinalItem()
13576
    {
13577
        $finalItem = $this->getFinalItem();
13578
        $doc = DocumentManager::get_document_data_by_id(
13579
            $finalItem->path,
13580
            $this->cc
13581
        );
13582
        if ($doc && file_exists($doc['absolute_path'])) {
13583
            return file_get_contents($doc['absolute_path']);
13584
        }
13585
13586
        return '';
13587
    }
13588
}
13589