Passed
Push — master ( ea32e7...68af9c )
by Julito
09:25
created

learnpath   F

Complexity

Total Complexity 1816

Size/Duplication

Total Lines 13510
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7473
c 5
b 1
f 0
dl 0
loc 13510
rs 0.8
wmc 1816

217 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
use Chamilo\CoreBundle\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
        $name = Database::escape_string($name);
735
        $categoryId = (int) $categoryId;
736
737
        // Session id.
738
        $session_id = api_get_session_id();
739
        $userId = empty($userId) ? api_get_user_id() : $userId;
740
        $check_name = "SELECT * FROM $tbl_lp
741
                       WHERE c_id = $course_id AND name = '$name'";
742
743
        $res_name = Database::query($check_name);
744
745
        if (empty($publicated_on)) {
746
            $publicated_on = null;
747
        } else {
748
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
749
        }
750
751
        if (empty($expired_on)) {
752
            $expired_on = null;
753
        } else {
754
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
755
        }
756
757
        while (Database::num_rows($res_name)) {
758
            // There is already one such name, update the current one a bit.
759
            $i++;
760
            $name = $name.' - '.$i;
761
            $check_name = "SELECT * FROM $tbl_lp 
762
                           WHERE c_id = $course_id AND name = '$name'";
763
            $res_name = Database::query($check_name);
764
        }
765
        // New name does not exist yet; keep it.
766
        // Escape description.
767
        // Kevin: added htmlentities().
768
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
769
        $type = 1;
770
        switch ($learnpath) {
771
            case 'guess':
772
                break;
773
            case 'dokeos':
774
            case 'chamilo':
775
                $type = 1;
776
                break;
777
            case 'aicc':
778
                break;
779
        }
780
781
        switch ($origin) {
782
            case 'zip':
783
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
784
                break;
785
            case 'manual':
786
            default:
787
                $get_max = "SELECT MAX(display_order) 
788
                            FROM $tbl_lp WHERE c_id = $course_id";
789
                $res_max = Database::query($get_max);
790
                if (Database::num_rows($res_max) < 1) {
791
                    $dsp = 1;
792
                } else {
793
                    $row = Database::fetch_array($res_max);
794
                    $dsp = $row[0] + 1;
795
                }
796
797
                $params = [
798
                    'c_id' => $course_id,
799
                    'lp_type' => $type,
800
                    'name' => $name,
801
                    'description' => $description,
802
                    'path' => '',
803
                    'default_view_mod' => 'embedded',
804
                    'default_encoding' => 'UTF-8',
805
                    'display_order' => $dsp,
806
                    'content_maker' => 'Chamilo',
807
                    'content_local' => 'local',
808
                    'js_lib' => '',
809
                    'session_id' => $session_id,
810
                    'created_on' => api_get_utc_datetime(),
811
                    'modified_on' => api_get_utc_datetime(),
812
                    'publicated_on' => $publicated_on,
813
                    'expired_on' => $expired_on,
814
                    'category_id' => $categoryId,
815
                    'force_commit' => 0,
816
                    'content_license' => '',
817
                    'debug' => 0,
818
                    'theme' => '',
819
                    'preview_image' => '',
820
                    'author' => '',
821
                    'prerequisite' => 0,
822
                    'hide_toc_frame' => 0,
823
                    'seriousgame_mode' => 0,
824
                    'autolaunch' => 0,
825
                    'max_attempts' => 0,
826
                    'subscribe_users' => 0,
827
                    'accumulate_scorm_time' => 1,
828
                ];
829
                $id = Database::insert($tbl_lp, $params);
830
831
                if ($id > 0) {
832
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
833
                    Database::query($sql);
834
835
                    // Insert into item_property.
836
                    api_item_property_update(
837
                        $courseInfo,
838
                        TOOL_LEARNPATH,
839
                        $id,
840
                        'LearnpathAdded',
841
                        $userId
842
                    );
843
                    api_set_default_visibility(
844
                        $id,
845
                        TOOL_LEARNPATH,
846
                        0,
847
                        $courseInfo,
848
                        $session_id,
849
                        $userId
850
                    );
851
852
                    return $id;
853
                }
854
                break;
855
        }
856
    }
857
858
    /**
859
     * Auto completes the parents of an item in case it's been completed or passed.
860
     *
861
     * @param int $item Optional ID of the item from which to look for parents
862
     */
863
    public function autocomplete_parents($item)
864
    {
865
        $debug = $this->debug;
866
867
        if (empty($item)) {
868
            $item = $this->current;
869
        }
870
871
        $currentItem = $this->getItem($item);
872
        if ($currentItem) {
873
            $parent_id = $currentItem->get_parent();
874
            $parent = $this->getItem($parent_id);
875
            if ($parent) {
876
                // if $item points to an object and there is a parent.
877
                if ($debug) {
878
                    error_log(
879
                        'Autocompleting parent of item '.$item.' '.
880
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
881
                        0
882
                    );
883
                }
884
885
                // New experiment including failed and browsed in completed status.
886
                //$current_status = $currentItem->get_status();
887
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
888
                // Fixes chapter auto complete
889
                if (true) {
890
                    // If the current item is completed or passes or succeeded.
891
                    $updateParentStatus = true;
892
                    if ($debug) {
893
                        error_log('Status of current item is alright');
894
                    }
895
896
                    foreach ($parent->get_children() as $childItemId) {
897
                        $childItem = $this->getItem($childItemId);
898
899
                        // If children was not set try to get the info
900
                        if (empty($childItem->db_item_view_id)) {
901
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
902
                        }
903
904
                        // Check all his brothers (parent's children) for completion status.
905
                        if ($childItemId != $item) {
906
                            if ($debug) {
907
                                error_log(
908
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
909
                                    0
910
                                );
911
                            }
912
                            // Trying completing parents of failed and browsed items as well.
913
                            if ($childItem->status_is(
914
                                [
915
                                    'completed',
916
                                    'passed',
917
                                    'succeeded',
918
                                    'browsed',
919
                                    'failed',
920
                                ]
921
                            )
922
                            ) {
923
                                // Keep completion status to true.
924
                                continue;
925
                            } else {
926
                                if ($debug > 2) {
927
                                    error_log(
928
                                        '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,
929
                                        0
930
                                    );
931
                                }
932
                                $updateParentStatus = false;
933
                                break;
934
                            }
935
                        }
936
                    }
937
938
                    if ($updateParentStatus) {
939
                        // If all the children were completed:
940
                        $parent->set_status('completed');
941
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
942
                        // Force the status to "completed"
943
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
944
                        $this->update_queue[$parent->get_id()] = 'completed';
945
                        if ($debug) {
946
                            error_log(
947
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
948
                                print_r($this->update_queue, 1),
949
                                0
950
                            );
951
                        }
952
                        // Recursive call.
953
                        $this->autocomplete_parents($parent->get_id());
954
                    }
955
                }
956
            } else {
957
                if ($debug) {
958
                    error_log("Parent #$parent_id does not exists");
959
                }
960
            }
961
        } else {
962
            if ($debug) {
963
                error_log("#$item is an item that doesn't have parents");
964
            }
965
        }
966
    }
967
968
    /**
969
     * Closes the current resource.
970
     *
971
     * Stops the timer
972
     * Saves into the database if required
973
     * Clears the current resource data from this object
974
     *
975
     * @return bool True on success, false on failure
976
     */
977
    public function close()
978
    {
979
        if (empty($this->lp_id)) {
980
            $this->error = 'Trying to close this learnpath but no ID is set';
981
982
            return false;
983
        }
984
        $this->current_time_stop = time();
985
        $this->ordered_items = [];
986
        $this->index = 0;
987
        unset($this->lp_id);
988
        //unset other stuff
989
        return true;
990
    }
991
992
    /**
993
     * Static admin function allowing removal of a learnpath.
994
     *
995
     * @param array  $courseInfo
996
     * @param int    $id         Learnpath ID
997
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
998
     *
999
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1000
     */
1001
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1002
    {
1003
        $course_id = api_get_course_int_id();
1004
        if (!empty($courseInfo)) {
1005
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1006
        }
1007
1008
        // TODO: Implement a way of getting this to work when the current object is not set.
1009
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1010
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1011
        if (!empty($id) && ($id != $this->lp_id)) {
1012
            return false;
1013
        }
1014
1015
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1016
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1017
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1018
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1019
1020
        // Delete lp item id.
1021
        foreach ($this->items as $lpItemId => $dummy) {
1022
            $sql = "DELETE FROM $lp_item_view
1023
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1024
            Database::query($sql);
1025
        }
1026
1027
        // Proposed by Christophe (nickname: clefevre)
1028
        $sql = "DELETE FROM $lp_item
1029
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1030
        Database::query($sql);
1031
1032
        $sql = "DELETE FROM $lp_view 
1033
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1034
        Database::query($sql);
1035
1036
        self::toggle_publish($this->lp_id, 'i');
1037
1038
        if ($this->type == 2 || $this->type == 3) {
1039
            // This is a scorm learning path, delete the files as well.
1040
            $sql = "SELECT path FROM $lp
1041
                    WHERE iid = ".$this->lp_id;
1042
            $res = Database::query($sql);
1043
            if (Database::num_rows($res) > 0) {
1044
                $row = Database::fetch_array($res);
1045
                $path = $row['path'];
1046
                $sql = "SELECT id FROM $lp
1047
                        WHERE 
1048
                            c_id = $course_id AND
1049
                            path = '$path' AND 
1050
                            iid != ".$this->lp_id;
1051
                $res = Database::query($sql);
1052
                if (Database::num_rows($res) > 0) {
1053
                    // Another learning path uses this directory, so don't delete it.
1054
                    if ($this->debug > 2) {
1055
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1056
                    }
1057
                } else {
1058
                    // No other LP uses that directory, delete it.
1059
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1060
                    // The absolute system path for this course.
1061
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1062
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1063
                        if ($this->debug > 2) {
1064
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1065
                        }
1066
                        // Proposed by Christophe (clefevre).
1067
                        if (strcmp(substr($path, -2), "/.") == 0) {
1068
                            $path = substr($path, 0, -1); // Remove "." at the end.
1069
                        }
1070
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1071
                        rmdirr($course_scorm_dir.$path);
1072
                    }
1073
                }
1074
            }
1075
        }
1076
1077
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1078
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1079
        // Delete tools
1080
        $sql = "DELETE FROM $tbl_tool
1081
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1082
        Database::query($sql);
1083
1084
        $sql = "DELETE FROM $lp 
1085
                WHERE iid = ".$this->lp_id;
1086
        Database::query($sql);
1087
        // Updates the display order of all lps.
1088
        $this->update_display_order();
1089
1090
        api_item_property_update(
1091
            api_get_course_info(),
1092
            TOOL_LEARNPATH,
1093
            $this->lp_id,
1094
            'delete',
1095
            api_get_user_id()
1096
        );
1097
1098
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1099
            api_get_course_id(),
1100
            4,
1101
            $id,
1102
            api_get_session_id()
1103
        );
1104
1105
        if ($link_info !== false) {
1106
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1107
        }
1108
1109
        if (api_get_setting('search_enabled') == 'true') {
1110
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1111
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1112
        }
1113
    }
1114
1115
    /**
1116
     * Removes all the children of one item - dangerous!
1117
     *
1118
     * @param int $id Element ID of which children have to be removed
1119
     *
1120
     * @return int Total number of children removed
1121
     */
1122
    public function delete_children_items($id)
1123
    {
1124
        $course_id = $this->course_info['real_id'];
1125
1126
        $num = 0;
1127
        $id = (int) $id;
1128
        if (empty($id) || empty($course_id)) {
1129
            return false;
1130
        }
1131
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1132
        $sql = "SELECT * FROM $lp_item 
1133
                WHERE c_id = $course_id AND parent_item_id = $id";
1134
        $res = Database::query($sql);
1135
        while ($row = Database::fetch_array($res)) {
1136
            $num += $this->delete_children_items($row['iid']);
1137
            $sql = "DELETE FROM $lp_item 
1138
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1139
            Database::query($sql);
1140
            $num++;
1141
        }
1142
1143
        return $num;
1144
    }
1145
1146
    /**
1147
     * Removes an item from the current learnpath.
1148
     *
1149
     * @param int $id Elem ID (0 if first)
1150
     *
1151
     * @return int Number of elements moved
1152
     *
1153
     * @todo implement resource removal
1154
     */
1155
    public function delete_item($id)
1156
    {
1157
        $course_id = api_get_course_int_id();
1158
        $id = (int) $id;
1159
        // TODO: Implement the resource removal.
1160
        if (empty($id) || empty($course_id)) {
1161
            return false;
1162
        }
1163
        // First select item to get previous, next, and display order.
1164
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1165
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1166
        $res_sel = Database::query($sql_sel);
1167
        if (Database::num_rows($res_sel) < 1) {
1168
            return false;
1169
        }
1170
        $row = Database::fetch_array($res_sel);
1171
        $previous = $row['previous_item_id'];
1172
        $next = $row['next_item_id'];
1173
        $display = $row['display_order'];
1174
        $parent = $row['parent_item_id'];
1175
        $lp = $row['lp_id'];
1176
        // Delete children items.
1177
        $this->delete_children_items($id);
1178
        // Now delete the item.
1179
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1180
        Database::query($sql_del);
1181
        // Now update surrounding items.
1182
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1183
                    WHERE iid = $previous";
1184
        Database::query($sql_upd);
1185
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1186
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1187
        Database::query($sql_upd);
1188
        // Now update all following items with new display order.
1189
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1190
                    WHERE 
1191
                        c_id = $course_id AND 
1192
                        lp_id = $lp AND 
1193
                        parent_item_id = $parent AND 
1194
                        display_order > $display";
1195
        Database::query($sql_all);
1196
1197
        //Removing prerequisites since the item will not longer exist
1198
        $sql_all = "UPDATE $lp_item SET prerequisite = '' 
1199
                    WHERE c_id = $course_id AND prerequisite = $id";
1200
        Database::query($sql_all);
1201
1202
        $sql = "UPDATE $lp_item
1203
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1204
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1205
        Database::query($sql);
1206
1207
        // Remove from search engine if enabled.
1208
        if (api_get_setting('search_enabled') === 'true') {
1209
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1210
            $sql = 'SELECT * FROM %s 
1211
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1212
                    LIMIT 1';
1213
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1214
            $res = Database::query($sql);
1215
            if (Database::num_rows($res) > 0) {
1216
                $row2 = Database::fetch_array($res);
1217
                $di = new ChamiloIndexer();
1218
                $di->remove_document($row2['search_did']);
1219
            }
1220
            $sql = 'DELETE FROM %s 
1221
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d 
1222
                    LIMIT 1';
1223
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1224
            Database::query($sql);
1225
        }
1226
    }
1227
1228
    /**
1229
     * Updates an item's content in place.
1230
     *
1231
     * @param int    $id               Element ID
1232
     * @param int    $parent           Parent item ID
1233
     * @param int    $previous         Previous item ID
1234
     * @param string $title            Item title
1235
     * @param string $description      Item description
1236
     * @param string $prerequisites    Prerequisites (optional)
1237
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1238
     * @param int    $max_time_allowed
1239
     * @param string $url
1240
     *
1241
     * @return bool True on success, false on error
1242
     */
1243
    public function edit_item(
1244
        $id,
1245
        $parent,
1246
        $previous,
1247
        $title,
1248
        $description,
1249
        $prerequisites = '0',
1250
        $audio = [],
1251
        $max_time_allowed = 0,
1252
        $url = ''
1253
    ) {
1254
        $course_id = api_get_course_int_id();
1255
        $_course = api_get_course_info();
1256
        $id = (int) $id;
1257
1258
        if (empty($max_time_allowed)) {
1259
            $max_time_allowed = 0;
1260
        }
1261
1262
        if (empty($id) || empty($_course)) {
1263
            return false;
1264
        }
1265
1266
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1267
        $sql = "SELECT * FROM $tbl_lp_item 
1268
                WHERE iid = $id";
1269
        $res_select = Database::query($sql);
1270
        $row_select = Database::fetch_array($res_select);
1271
        $audio_update_sql = '';
1272
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1273
            // Create the audio folder if it does not exist yet.
1274
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1275
            if (!is_dir($filepath.'audio')) {
1276
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1277
                $audio_id = DocumentManager::addDocument(
1278
                    $_course,
1279
                    '/audio',
1280
                    'folder',
1281
                    0,
1282
                    'audio'
1283
                );
1284
            }
1285
1286
            // Upload file in documents.
1287
            $pi = pathinfo($audio['name']);
1288
            if ($pi['extension'] === 'mp3') {
1289
                $c_det = api_get_course_info($this->cc);
1290
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1291
                $path = handle_uploaded_document(
1292
                    $c_det,
1293
                    $audio,
1294
                    $bp,
1295
                    '/audio',
1296
                    api_get_user_id(),
1297
                    0,
1298
                    null,
1299
                    0,
1300
                    'rename',
1301
                    false,
1302
                    0
1303
                );
1304
                $path = substr($path, 7);
1305
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1306
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1307
            }
1308
        }
1309
1310
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1311
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1312
1313
        // TODO: htmlspecialchars to be checked for encoding related problems.
1314
        if ($same_parent && $same_previous) {
1315
            // Only update title and description.
1316
            $sql = "UPDATE $tbl_lp_item
1317
                    SET title = '".Database::escape_string($title)."',
1318
                        prerequisite = '".$prerequisites."',
1319
                        description = '".Database::escape_string($description)."'
1320
                        ".$audio_update_sql.",
1321
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1322
                    WHERE iid = $id";
1323
            Database::query($sql);
1324
        } else {
1325
            $old_parent = $row_select['parent_item_id'];
1326
            $old_previous = $row_select['previous_item_id'];
1327
            $old_next = $row_select['next_item_id'];
1328
            $old_order = $row_select['display_order'];
1329
            $old_prerequisite = $row_select['prerequisite'];
1330
            $old_max_time_allowed = $row_select['max_time_allowed'];
1331
1332
            /* BEGIN -- virtually remove the current item id */
1333
            /* for the next and previous item it is like the current item doesn't exist anymore */
1334
            if ($old_previous != 0) {
1335
                // Next
1336
                $sql = "UPDATE $tbl_lp_item
1337
                        SET next_item_id = $old_next
1338
                        WHERE iid = $old_previous";
1339
                Database::query($sql);
1340
            }
1341
1342
            if (!empty($old_next)) {
1343
                // Previous
1344
                $sql = "UPDATE $tbl_lp_item
1345
                        SET previous_item_id = $old_previous
1346
                        WHERE iid = $old_next";
1347
                Database::query($sql);
1348
            }
1349
1350
            // display_order - 1 for every item with a display_order
1351
            // bigger then the display_order of the current item.
1352
            $sql = "UPDATE $tbl_lp_item
1353
                    SET display_order = display_order - 1
1354
                    WHERE
1355
                        c_id = $course_id AND
1356
                        display_order > $old_order AND
1357
                        lp_id = ".$this->lp_id." AND
1358
                        parent_item_id = $old_parent";
1359
            Database::query($sql);
1360
            /* END -- virtually remove the current item id */
1361
1362
            /* BEGIN -- update the current item id to his new location */
1363
            if ($previous == 0) {
1364
                // Select the data of the item that should come after the current item.
1365
                $sql = "SELECT id, display_order
1366
                        FROM $tbl_lp_item
1367
                        WHERE
1368
                            c_id = $course_id AND
1369
                            lp_id = ".$this->lp_id." AND
1370
                            parent_item_id = $parent AND
1371
                            previous_item_id = $previous";
1372
                $res_select_old = Database::query($sql);
1373
                $row_select_old = Database::fetch_array($res_select_old);
1374
1375
                // If the new parent didn't have children before.
1376
                if (Database::num_rows($res_select_old) == 0) {
1377
                    $new_next = 0;
1378
                    $new_order = 1;
1379
                } else {
1380
                    $new_next = $row_select_old['id'];
1381
                    $new_order = $row_select_old['display_order'];
1382
                }
1383
            } else {
1384
                // Select the data of the item that should come before the current item.
1385
                $sql = "SELECT next_item_id, display_order
1386
                        FROM $tbl_lp_item
1387
                        WHERE iid = $previous";
1388
                $res_select_old = Database::query($sql);
1389
                $row_select_old = Database::fetch_array($res_select_old);
1390
                $new_next = $row_select_old['next_item_id'];
1391
                $new_order = $row_select_old['display_order'] + 1;
1392
            }
1393
1394
            // TODO: htmlspecialchars to be checked for encoding related problems.
1395
            // Update the current item with the new data.
1396
            $sql = "UPDATE $tbl_lp_item
1397
                    SET
1398
                        title = '".Database::escape_string($title)."',
1399
                        description = '".Database::escape_string($description)."',
1400
                        parent_item_id = $parent,
1401
                        previous_item_id = $previous,
1402
                        next_item_id = $new_next,
1403
                        display_order = $new_order
1404
                        $audio_update_sql
1405
                    WHERE iid = $id";
1406
            Database::query($sql);
1407
1408
            if ($previous != 0) {
1409
                // Update the previous item's next_item_id.
1410
                $sql = "UPDATE $tbl_lp_item
1411
                        SET next_item_id = $id
1412
                        WHERE iid = $previous";
1413
                Database::query($sql);
1414
            }
1415
1416
            if (!empty($new_next)) {
1417
                // Update the next item's previous_item_id.
1418
                $sql = "UPDATE $tbl_lp_item
1419
                        SET previous_item_id = $id
1420
                        WHERE iid = $new_next";
1421
                Database::query($sql);
1422
            }
1423
1424
            if ($old_prerequisite != $prerequisites) {
1425
                $sql = "UPDATE $tbl_lp_item
1426
                        SET prerequisite = '$prerequisites'
1427
                        WHERE iid = $id";
1428
                Database::query($sql);
1429
            }
1430
1431
            if ($old_max_time_allowed != $max_time_allowed) {
1432
                // update max time allowed
1433
                $sql = "UPDATE $tbl_lp_item
1434
                        SET max_time_allowed = $max_time_allowed
1435
                        WHERE iid = $id";
1436
                Database::query($sql);
1437
            }
1438
1439
            // Update all the items with the same or a bigger display_order than the current item.
1440
            $sql = "UPDATE $tbl_lp_item
1441
                    SET display_order = display_order + 1
1442
                    WHERE
1443
                       c_id = $course_id AND
1444
                       lp_id = ".$this->get_id()." AND
1445
                       iid <> $id AND
1446
                       parent_item_id = $parent AND
1447
                       display_order >= $new_order";
1448
            Database::query($sql);
1449
        }
1450
1451
        if ($row_select['item_type'] == 'link') {
1452
            $link = new Link();
1453
            $linkId = $row_select['path'];
1454
            $link->updateLink($linkId, $url);
1455
        }
1456
    }
1457
1458
    /**
1459
     * Updates an item's prereq in place.
1460
     *
1461
     * @param int    $id              Element ID
1462
     * @param string $prerequisite_id Prerequisite Element ID
1463
     * @param int    $minScore        Prerequisite min score
1464
     * @param int    $maxScore        Prerequisite max score
1465
     *
1466
     * @return bool True on success, false on error
1467
     */
1468
    public function edit_item_prereq(
1469
        $id,
1470
        $prerequisite_id,
1471
        $minScore = 0,
1472
        $maxScore = 100
1473
    ) {
1474
        $id = (int) $id;
1475
        $prerequisite_id = (int) $prerequisite_id;
1476
1477
        if (empty($id)) {
1478
            return false;
1479
        }
1480
1481
        if (empty($minScore) || $minScore < 0) {
1482
            $minScore = 0;
1483
        }
1484
1485
        if (empty($maxScore) || $maxScore < 0) {
1486
            $maxScore = 100;
1487
        }
1488
1489
        $minScore = floatval($minScore);
1490
        $maxScore = floatval($maxScore);
1491
1492
        if (empty($prerequisite_id)) {
1493
            $prerequisite_id = 'NULL';
1494
            $minScore = 0;
1495
            $maxScore = 100;
1496
        }
1497
1498
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1499
        $sql = " UPDATE $tbl_lp_item
1500
                 SET
1501
                    prerequisite = $prerequisite_id ,
1502
                    prerequisite_min_score = $minScore ,
1503
                    prerequisite_max_score = $maxScore
1504
                 WHERE iid = $id";
1505
1506
        Database::query($sql);
1507
1508
        return true;
1509
    }
1510
1511
    /**
1512
     * Get the specific prefix index terms of this learning path.
1513
     *
1514
     * @param string $prefix
1515
     *
1516
     * @return array Array of terms
1517
     */
1518
    public function get_common_index_terms_by_prefix($prefix)
1519
    {
1520
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1521
        $terms = get_specific_field_values_list_by_prefix(
1522
            $prefix,
1523
            $this->cc,
1524
            TOOL_LEARNPATH,
1525
            $this->lp_id
1526
        );
1527
        $prefix_terms = [];
1528
        if (!empty($terms)) {
1529
            foreach ($terms as $term) {
1530
                $prefix_terms[] = $term['value'];
1531
            }
1532
        }
1533
1534
        return $prefix_terms;
1535
    }
1536
1537
    /**
1538
     * Gets the number of items currently completed.
1539
     *
1540
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1541
     *
1542
     * @return int The number of items currently completed
1543
     */
1544
    public function get_complete_items_count($failedStatusException = false)
1545
    {
1546
        $i = 0;
1547
        $completedStatusList = [
1548
            'completed',
1549
            'passed',
1550
            'succeeded',
1551
            'browsed',
1552
        ];
1553
1554
        if (!$failedStatusException) {
1555
            $completedStatusList[] = 'failed';
1556
        }
1557
1558
        foreach ($this->items as $id => $dummy) {
1559
            // Trying failed and browsed considered "progressed" as well.
1560
            if ($this->items[$id]->status_is($completedStatusList) &&
1561
                $this->items[$id]->get_type() != 'dir'
1562
            ) {
1563
                $i++;
1564
            }
1565
        }
1566
1567
        return $i;
1568
    }
1569
1570
    /**
1571
     * Gets the current item ID.
1572
     *
1573
     * @return int The current learnpath item id
1574
     */
1575
    public function get_current_item_id()
1576
    {
1577
        $current = 0;
1578
        if (!empty($this->current)) {
1579
            $current = (int) $this->current;
1580
        }
1581
1582
        return $current;
1583
    }
1584
1585
    /**
1586
     * Force to get the first learnpath item id.
1587
     *
1588
     * @return int The current learnpath item id
1589
     */
1590
    public function get_first_item_id()
1591
    {
1592
        $current = 0;
1593
        if (is_array($this->ordered_items)) {
1594
            $current = $this->ordered_items[0];
1595
        }
1596
1597
        return $current;
1598
    }
1599
1600
    /**
1601
     * Gets the total number of items available for viewing in this SCORM.
1602
     *
1603
     * @return int The total number of items
1604
     */
1605
    public function get_total_items_count()
1606
    {
1607
        return count($this->items);
1608
    }
1609
1610
    /**
1611
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1612
     *
1613
     * @return int The total no-chapters number of items
1614
     */
1615
    public function getTotalItemsCountWithoutDirs()
1616
    {
1617
        $total = 0;
1618
        $typeListNotToCount = self::getChapterTypes();
1619
        foreach ($this->items as $temp2) {
1620
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1621
                $total++;
1622
            }
1623
        }
1624
1625
        return $total;
1626
    }
1627
1628
    /**
1629
     *  Sets the first element URL.
1630
     */
1631
    public function first()
1632
    {
1633
        if ($this->debug > 0) {
1634
            error_log('In learnpath::first()', 0);
1635
            error_log('$this->last_item_seen '.$this->last_item_seen);
1636
        }
1637
1638
        // Test if the last_item_seen exists and is not a dir.
1639
        if (count($this->ordered_items) == 0) {
1640
            $this->index = 0;
1641
        }
1642
1643
        if (!empty($this->last_item_seen) &&
1644
            !empty($this->items[$this->last_item_seen]) &&
1645
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1646
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1647
            //&& !$this->items[$this->last_item_seen]->is_done()
1648
        ) {
1649
            if ($this->debug > 2) {
1650
                error_log(
1651
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1652
                    $this->items[$this->last_item_seen]->get_type()
1653
                );
1654
            }
1655
            $index = -1;
1656
            foreach ($this->ordered_items as $myindex => $item_id) {
1657
                if ($item_id == $this->last_item_seen) {
1658
                    $index = $myindex;
1659
                    break;
1660
                }
1661
            }
1662
            if ($index == -1) {
1663
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1664
                if ($this->debug > 2) {
1665
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1666
                }
1667
1668
                return false;
1669
            } else {
1670
                $this->last = $this->last_item_seen;
1671
                $this->current = $this->last_item_seen;
1672
                $this->index = $index;
1673
            }
1674
        } else {
1675
            if ($this->debug > 2) {
1676
                error_log('In learnpath::first() - No last item seen', 0);
1677
            }
1678
            $index = 0;
1679
            // Loop through all ordered items and stop at the first item that is
1680
            // not a directory *and* that has not been completed yet.
1681
            while (!empty($this->ordered_items[$index]) &&
1682
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1683
                (
1684
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1685
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1686
                ) && $index < $this->max_ordered_items) {
1687
                $index++;
1688
            }
1689
1690
            $this->last = $this->current;
1691
            // current is
1692
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1693
            $this->index = $index;
1694
            if ($this->debug > 2) {
1695
                error_log('$index '.$index);
1696
                error_log('In learnpath::first() - No last item seen');
1697
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1698
            }
1699
        }
1700
        if ($this->debug > 2) {
1701
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1702
        }
1703
    }
1704
1705
    /**
1706
     * Gets the js library from the database.
1707
     *
1708
     * @return string The name of the javascript library to be used
1709
     */
1710
    public function get_js_lib()
1711
    {
1712
        $lib = '';
1713
        if (!empty($this->js_lib)) {
1714
            $lib = $this->js_lib;
1715
        }
1716
1717
        return $lib;
1718
    }
1719
1720
    /**
1721
     * Gets the learnpath database ID.
1722
     *
1723
     * @return int Learnpath ID in the lp table
1724
     */
1725
    public function get_id()
1726
    {
1727
        if (!empty($this->lp_id)) {
1728
            return (int) $this->lp_id;
1729
        }
1730
1731
        return 0;
1732
    }
1733
1734
    /**
1735
     * Gets the last element URL.
1736
     *
1737
     * @return string URL to load into the viewer
1738
     */
1739
    public function get_last()
1740
    {
1741
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1742
        if (count($this->ordered_items) > 0) {
1743
            $this->index = count($this->ordered_items) - 1;
1744
1745
            return $this->ordered_items[$this->index];
1746
        }
1747
1748
        return false;
1749
    }
1750
1751
    /**
1752
     * Get the last element in the first level.
1753
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1754
     *
1755
     * @return mixed
1756
     */
1757
    public function getLastInFirstLevel()
1758
    {
1759
        try {
1760
            $lastId = Database::getManager()
1761
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1762
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1763
                ->setMaxResults(1)
1764
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1765
                ->getSingleScalarResult();
1766
1767
            return $lastId;
1768
        } catch (Exception $exception) {
1769
            return 0;
1770
        }
1771
    }
1772
1773
    /**
1774
     * Gets the navigation bar for the learnpath display screen.
1775
     *
1776
     * @param string $barId
1777
     *
1778
     * @return string The HTML string to use as a navigation bar
1779
     */
1780
    public function get_navigation_bar($barId = '')
1781
    {
1782
        if (empty($barId)) {
1783
            $barId = 'control-top';
1784
        }
1785
        $lpId = $this->lp_id;
1786
        $mycurrentitemid = $this->get_current_item_id();
1787
1788
        $reportingText = get_lang('Reporting');
1789
        $previousText = get_lang('ScormPrevious');
1790
        $nextText = get_lang('ScormNext');
1791
        $fullScreenText = get_lang('ScormExitFullScreen');
1792
1793
        $settings = api_get_configuration_value('lp_view_settings');
1794
        $display = isset($settings['display']) ? $settings['display'] : false;
1795
        $reportingIcon = '
1796
            <a class="icon-toolbar" 
1797
                id="stats_link"
1798
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'" 
1799
                onclick="window.parent.API.save_asset(); return true;" 
1800
                target="content_name" title="'.$reportingText.'">
1801
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1802
            </a>';
1803
1804
        if (!empty($display)) {
1805
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1806
            if ($showReporting === false) {
1807
                $reportingIcon = '';
1808
            }
1809
        }
1810
1811
        $hideArrows = false;
1812
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1813
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1814
        }
1815
1816
        $previousIcon = '';
1817
        $nextIcon = '';
1818
        if ($hideArrows === false) {
1819
            $previousIcon = '
1820
                <a class="icon-toolbar" id="scorm-previous" href="#" 
1821
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1822
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1823
                </a>';
1824
1825
            $nextIcon = '
1826
                <a class="icon-toolbar" id="scorm-next" href="#" 
1827
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1828
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1829
                </a>';
1830
        }
1831
1832
        if ($this->mode === 'fullscreen') {
1833
            $navbar = '
1834
                  <span id="'.$barId.'" class="buttons">
1835
                    '.$reportingIcon.'
1836
                    '.$previousIcon.'                    
1837
                    '.$nextIcon.'
1838
                    <a class="icon-toolbar" id="view-embedded" 
1839
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1840
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1841
                    </a>
1842
                  </span>';
1843
        } else {
1844
            $navbar = '
1845
                 <span id="'.$barId.'" class="buttons text-right">
1846
                    '.$reportingIcon.'
1847
                    '.$previousIcon.'
1848
                    '.$nextIcon.'               
1849
                </span>';
1850
        }
1851
1852
        return $navbar;
1853
    }
1854
1855
    /**
1856
     * Gets the next resource in queue (url).
1857
     *
1858
     * @return string URL to load into the viewer
1859
     */
1860
    public function get_next_index()
1861
    {
1862
        // TODO
1863
        $index = $this->index;
1864
        $index++;
1865
        while (
1866
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1867
            $index < $this->max_ordered_items
1868
        ) {
1869
            $index++;
1870
            if ($index == $this->max_ordered_items) {
1871
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1872
                    return $this->index;
1873
                }
1874
1875
                return $index;
1876
            }
1877
        }
1878
        if (empty($this->ordered_items[$index])) {
1879
            return $this->index;
1880
        }
1881
1882
        return $index;
1883
    }
1884
1885
    /**
1886
     * Gets item_id for the next element.
1887
     *
1888
     * @return int Next item (DB) ID
1889
     */
1890
    public function get_next_item_id()
1891
    {
1892
        $new_index = $this->get_next_index();
1893
        if (!empty($new_index)) {
1894
            if (isset($this->ordered_items[$new_index])) {
1895
                return $this->ordered_items[$new_index];
1896
            }
1897
        }
1898
1899
        return 0;
1900
    }
1901
1902
    /**
1903
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1904
     *
1905
     * Generally, the package provided is in the form of a zip file, so the function
1906
     * has been written to test a zip file. If not a zip, the function will return the
1907
     * default return value: ''
1908
     *
1909
     * @param string $file_path the path to the file
1910
     * @param string $file_name the original name of the file
1911
     *
1912
     * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
1913
     */
1914
    public static function get_package_type($file_path, $file_name)
1915
    {
1916
        // Get name of the zip file without the extension.
1917
        $file_info = pathinfo($file_name);
1918
        $extension = $file_info['extension']; // Extension only.
1919
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1920
                'dll',
1921
                'exe',
1922
            ])) {
1923
            return 'oogie';
1924
        }
1925
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1926
                'dll',
1927
                'exe',
1928
            ])) {
1929
            return 'woogie';
1930
        }
1931
1932
        $zipFile = new PclZip($file_path);
1933
        // Check the zip content (real size and file extension).
1934
        $zipContentArray = $zipFile->listContent();
1935
        $package_type = '';
1936
        $manifest = '';
1937
        $aicc_match_crs = 0;
1938
        $aicc_match_au = 0;
1939
        $aicc_match_des = 0;
1940
        $aicc_match_cst = 0;
1941
1942
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
1943
        if (is_array($zipContentArray) && count($zipContentArray) > 0) {
1944
            foreach ($zipContentArray as $thisContent) {
1945
                if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
1946
                    // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
1947
                } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
1948
                    $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
1949
                    $package_type = 'scorm';
1950
                    break; // Exit the foreach loop.
1951
                } elseif (
1952
                    preg_match('/aicc\//i', $thisContent['filename']) ||
1953
                    in_array(
1954
                        strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
1955
                        ['crs', 'au', 'des', 'cst']
1956
                    )
1957
                ) {
1958
                    $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
1959
                    switch ($ext) {
1960
                        case 'crs':
1961
                            $aicc_match_crs = 1;
1962
                            break;
1963
                        case 'au':
1964
                            $aicc_match_au = 1;
1965
                            break;
1966
                        case 'des':
1967
                            $aicc_match_des = 1;
1968
                            break;
1969
                        case 'cst':
1970
                            $aicc_match_cst = 1;
1971
                            break;
1972
                        default:
1973
                            break;
1974
                    }
1975
                    //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
1976
                } else {
1977
                    $package_type = '';
1978
                }
1979
            }
1980
        }
1981
1982
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
1983
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
1984
            $package_type = 'aicc';
1985
        }
1986
1987
        // Try with chamilo course builder
1988
        if (empty($package_type)) {
1989
            $package_type = 'chamilo';
1990
        }
1991
1992
        return $package_type;
1993
    }
1994
1995
    /**
1996
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
1997
     *
1998
     * @return string URL to load into the viewer
1999
     */
2000
    public function get_previous_index()
2001
    {
2002
        $index = $this->index;
2003
        if (isset($this->ordered_items[$index - 1])) {
2004
            $index--;
2005
            while (isset($this->ordered_items[$index]) &&
2006
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2007
            ) {
2008
                $index--;
2009
                if ($index < 0) {
2010
                    return $this->index;
2011
                }
2012
            }
2013
        }
2014
2015
        return $index;
2016
    }
2017
2018
    /**
2019
     * Gets item_id for the next element.
2020
     *
2021
     * @return int Previous item (DB) ID
2022
     */
2023
    public function get_previous_item_id()
2024
    {
2025
        $index = $this->get_previous_index();
2026
2027
        return $this->ordered_items[$index];
2028
    }
2029
2030
    /**
2031
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2032
     *
2033
     * @param int    $lpItemId
2034
     * @param string $autostart
2035
     *
2036
     * @return string The mediaplayer HTML
2037
     */
2038
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2039
    {
2040
        $course_id = api_get_course_int_id();
2041
        $_course = api_get_course_info();
2042
        if (empty($_course)) {
2043
            return '';
2044
        }
2045
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2046
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2047
        $lpItemId = (int) $lpItemId;
2048
2049
        /** @var learnpathItem $item */
2050
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2051
        $itemViewId = 0;
2052
        if ($item) {
2053
            $itemViewId = (int) $item->db_item_view_id;
2054
        }
2055
2056
        // Getting all the information about the item.
2057
        $sql = "SELECT lpi.audio, lpi.item_type, lp_view.status 
2058
                FROM $tbl_lp_item as lpi
2059
                INNER JOIN $tbl_lp_item_view as lp_view
2060
                ON (lpi.iid = lp_view.lp_item_id)
2061
                WHERE
2062
                    lp_view.iid = $itemViewId AND
2063
                    lpi.iid = $lpItemId AND
2064
                    lp_view.c_id = $course_id";
2065
        $result = Database::query($sql);
2066
        $row = Database::fetch_assoc($result);
2067
        $output = '';
2068
2069
        if (!empty($row['audio'])) {
2070
            $list = $_SESSION['oLP']->get_toc();
2071
2072
            switch ($row['item_type']) {
2073
                case 'quiz':
2074
                    $type_quiz = false;
2075
                    foreach ($list as $toc) {
2076
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2077
                            $type_quiz = true;
2078
                        }
2079
                    }
2080
2081
                    if ($type_quiz) {
2082
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2083
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2084
                        } else {
2085
                            $autostart_audio = $autostart;
2086
                        }
2087
                    }
2088
                    break;
2089
                case TOOL_READOUT_TEXT:;
2090
                    $autostart_audio = 'false';
2091
                    break;
2092
                default:
2093
                    $autostart_audio = 'true';
2094
            }
2095
2096
            $courseInfo = api_get_course_info();
2097
            $audio = $row['audio'];
2098
2099
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio;
2100
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/audio/'.$audio.'?'.api_get_cidreq();
2101
2102
            if (!file_exists($file)) {
2103
                $lpPathInfo = $_SESSION['oLP']->generate_lp_folder(api_get_course_info());
2104
                $file = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio;
2105
                $url = api_get_path(WEB_COURSE_PATH).$_course['path'].'/document'.$lpPathInfo['dir'].$audio.'?'.api_get_cidreq();
2106
            }
2107
2108
            $player = Display::getMediaPlayer(
2109
                $file,
2110
                [
2111
                    'id' => 'lp_audio_media_player',
2112
                    'url' => $url,
2113
                    'autoplay' => $autostart_audio,
2114
                    'width' => '100%',
2115
                ]
2116
            );
2117
2118
            // The mp3 player.
2119
            $output = '<div id="container">';
2120
            $output .= $player;
2121
            $output .= '</div>';
2122
        }
2123
2124
        return $output;
2125
    }
2126
2127
    /**
2128
     * @param int   $studentId
2129
     * @param int   $prerequisite
2130
     * @param array $courseInfo
2131
     * @param int   $sessionId
2132
     *
2133
     * @return bool
2134
     */
2135
    public static function isBlockedByPrerequisite(
2136
        $studentId,
2137
        $prerequisite,
2138
        $courseInfo,
2139
        $sessionId
2140
    ) {
2141
        if (empty($courseInfo)) {
2142
            return false;
2143
        }
2144
2145
        $courseId = $courseInfo['real_id'];
2146
2147
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2148
        if ($allow) {
2149
            if (api_is_allowed_to_edit() ||
2150
                api_is_platform_admin(true) ||
2151
                api_is_drh() ||
2152
                api_is_coach($sessionId, $courseId, false)
2153
            ) {
2154
                return false;
2155
            }
2156
        }
2157
2158
        $isBlocked = false;
2159
        if (!empty($prerequisite)) {
2160
            $progress = self::getProgress(
2161
                $prerequisite,
2162
                $studentId,
2163
                $courseId,
2164
                $sessionId
2165
            );
2166
            if ($progress < 100) {
2167
                $isBlocked = true;
2168
            }
2169
2170
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2171
                // Block if it does not exceed minimum time
2172
                // Minimum time (in minutes) to pass the learning path
2173
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2174
2175
                if ($accumulateWorkTime > 0) {
2176
                    // Total time in course (sum of times in learning paths from course)
2177
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2178
2179
                    // Connect with the plugin_licences_course_session table
2180
                    // which indicates what percentage of the time applies
2181
                    // Minimum connection percentage
2182
                    $perc = 100;
2183
                    // Time from the course
2184
                    $tc = $accumulateWorkTimeTotal;
2185
2186
                    // Percentage of the learning paths
2187
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2188
                    // Minimum time for each learning path
2189
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2190
2191
                    // Spent time (in seconds) so far in the learning path
2192
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2193
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2194
2195
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2196
                        $isBlocked = true;
2197
                    }
2198
                }
2199
            }
2200
        }
2201
2202
        return $isBlocked;
2203
    }
2204
2205
    /**
2206
     * Checks if the learning path is visible for student after the progress
2207
     * of its prerequisite is completed, considering the time availability and
2208
     * the LP visibility.
2209
     *
2210
     * @param int   $lp_id
2211
     * @param int   $student_id
2212
     * @param array $courseInfo
2213
     * @param int   $sessionId
2214
     *
2215
     * @return bool
2216
     */
2217
    public static function is_lp_visible_for_student(
2218
        $lp_id,
2219
        $student_id,
2220
        $courseInfo = [],
2221
        $sessionId = 0
2222
    ) {
2223
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2224
        $lp_id = (int) $lp_id;
2225
        $sessionId = (int) $sessionId;
2226
2227
        if (empty($courseInfo)) {
2228
            return false;
2229
        }
2230
2231
        if (empty($sessionId)) {
2232
            $sessionId = api_get_session_id();
2233
        }
2234
2235
        $courseId = $courseInfo['real_id'];
2236
2237
        $itemInfo = api_get_item_property_info(
2238
            $courseId,
2239
            TOOL_LEARNPATH,
2240
            $lp_id,
2241
            $sessionId
2242
        );
2243
2244
        // If the item was deleted.
2245
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2246
            return false;
2247
        }
2248
2249
        // @todo remove this query and load the row info as a parameter
2250
        $table = Database::get_course_table(TABLE_LP_MAIN);
2251
        // Get current prerequisite
2252
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on
2253
                FROM $table
2254
                WHERE iid = $lp_id";
2255
        $rs = Database::query($sql);
2256
        $now = time();
2257
        if (Database::num_rows($rs) > 0) {
2258
            $row = Database::fetch_array($rs, 'ASSOC');
2259
            $prerequisite = $row['prerequisite'];
2260
            $is_visible = true;
2261
2262
            $isBlocked = self::isBlockedByPrerequisite(
2263
                $student_id,
2264
                $prerequisite,
2265
                $courseInfo,
2266
                $sessionId
2267
            );
2268
2269
            if ($isBlocked) {
2270
                $is_visible = false;
2271
            }
2272
2273
            // Also check the time availability of the LP
2274
            if ($is_visible) {
2275
                // Adding visibility restrictions
2276
                if (!empty($row['publicated_on'])) {
2277
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2278
                        $is_visible = false;
2279
                    }
2280
                }
2281
                // Blocking empty start times see BT#2800
2282
                global $_custom;
2283
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2284
                    $_custom['lps_hidden_when_no_start_date']
2285
                ) {
2286
                    if (empty($row['publicated_on'])) {
2287
                        $is_visible = false;
2288
                    }
2289
                }
2290
2291
                if (!empty($row['expired_on'])) {
2292
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2293
                        $is_visible = false;
2294
                    }
2295
                }
2296
            }
2297
2298
            if ($is_visible) {
2299
                $subscriptionSettings = self::getSubscriptionSettings();
2300
2301
                // Check if the subscription users/group to a LP is ON
2302
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2303
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2304
                ) {
2305
                    // Try group
2306
                    $is_visible = false;
2307
                    // Checking only the user visibility
2308
                    $userVisibility = api_get_item_visibility(
2309
                        $courseInfo,
2310
                        'learnpath',
2311
                        $row['id'],
2312
                        $sessionId,
2313
                        $student_id,
2314
                        'LearnpathSubscription'
2315
                    );
2316
2317
                    if ($userVisibility == 1) {
2318
                        $is_visible = true;
2319
                    } else {
2320
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2321
                        if (!empty($userGroups)) {
2322
                            foreach ($userGroups as $groupInfo) {
2323
                                $groupId = $groupInfo['iid'];
2324
                                $userVisibility = api_get_item_visibility(
2325
                                    $courseInfo,
2326
                                    'learnpath',
2327
                                    $row['id'],
2328
                                    $sessionId,
2329
                                    null,
2330
                                    'LearnpathSubscription',
2331
                                    $groupId
2332
                                );
2333
2334
                                if ($userVisibility == 1) {
2335
                                    $is_visible = true;
2336
                                    break;
2337
                                }
2338
                            }
2339
                        }
2340
                    }
2341
                }
2342
            }
2343
2344
            return $is_visible;
2345
        }
2346
2347
        return false;
2348
    }
2349
2350
    /**
2351
     * @param int $lpId
2352
     * @param int $userId
2353
     * @param int $courseId
2354
     * @param int $sessionId
2355
     *
2356
     * @return int
2357
     */
2358
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2359
    {
2360
        $lpId = (int) $lpId;
2361
        $userId = (int) $userId;
2362
        $courseId = (int) $courseId;
2363
        $sessionId = (int) $sessionId;
2364
2365
        $sessionCondition = api_get_session_condition($sessionId);
2366
        $table = Database::get_course_table(TABLE_LP_VIEW);
2367
        $sql = "SELECT progress FROM $table
2368
                WHERE
2369
                    c_id = $courseId AND
2370
                    lp_id = $lpId AND
2371
                    user_id = $userId $sessionCondition ";
2372
        $res = Database::query($sql);
2373
2374
        $progress = 0;
2375
        if (Database::num_rows($res) > 0) {
2376
            $row = Database::fetch_array($res);
2377
            $progress = (int) $row['progress'];
2378
        }
2379
2380
        return $progress;
2381
    }
2382
2383
    /**
2384
     * @param array $lpList
2385
     * @param int   $userId
2386
     * @param int   $courseId
2387
     * @param int   $sessionId
2388
     *
2389
     * @return array
2390
     */
2391
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2392
    {
2393
        $lpList = array_map('intval', $lpList);
2394
        if (empty($lpList)) {
2395
            return [];
2396
        }
2397
2398
        $lpList = implode("','", $lpList);
2399
2400
        $userId = (int) $userId;
2401
        $courseId = (int) $courseId;
2402
        $sessionId = (int) $sessionId;
2403
2404
        $sessionCondition = api_get_session_condition($sessionId);
2405
        $table = Database::get_course_table(TABLE_LP_VIEW);
2406
        $sql = "SELECT lp_id, progress FROM $table
2407
                WHERE
2408
                    c_id = $courseId AND
2409
                    lp_id IN ('".$lpList."') AND
2410
                    user_id = $userId $sessionCondition ";
2411
        $res = Database::query($sql);
2412
2413
        if (Database::num_rows($res) > 0) {
2414
            $list = [];
2415
            while ($row = Database::fetch_array($res)) {
2416
                $list[$row['lp_id']] = $row['progress'];
2417
            }
2418
2419
            return $list;
2420
        }
2421
2422
        return [];
2423
    }
2424
2425
    /**
2426
     * Displays a progress bar
2427
     * completed so far.
2428
     *
2429
     * @param int    $percentage Progress value to display
2430
     * @param string $text_add   Text to display near the progress value
2431
     *
2432
     * @return string HTML string containing the progress bar
2433
     */
2434
    public static function get_progress_bar($percentage = -1, $text_add = '')
2435
    {
2436
        $text = $percentage.$text_add;
2437
        $output = '<div class="progress">
2438
            <div id="progress_bar_value" 
2439
                class="progress-bar progress-bar-success" role="progressbar" 
2440
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2441
            '.$text.'
2442
            </div>
2443
        </div>';
2444
2445
        return $output;
2446
    }
2447
2448
    /**
2449
     * @param string $mode can be '%' or 'abs'
2450
     *                     otherwise this value will be used $this->progress_bar_mode
2451
     *
2452
     * @return string
2453
     */
2454
    public function getProgressBar($mode = null)
2455
    {
2456
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2457
2458
        return self::get_progress_bar($percentage, $text_add);
2459
    }
2460
2461
    /**
2462
     * Gets the progress bar info to display inside the progress bar.
2463
     * Also used by scorm_api.php.
2464
     *
2465
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2466
     *                     we display a number of completed elements per total elements
2467
     * @param int    $add  Additional steps to fake as completed
2468
     *
2469
     * @return array Percentage or number and symbol (% or /xx)
2470
     */
2471
    public function get_progress_bar_text($mode = '', $add = 0)
2472
    {
2473
        if (empty($mode)) {
2474
            $mode = $this->progress_bar_mode;
2475
        }
2476
        $total_items = $this->getTotalItemsCountWithoutDirs();
2477
        $completeItems = $this->get_complete_items_count();
2478
        if ($add != 0) {
2479
            $completeItems += $add;
2480
        }
2481
        $text = '';
2482
        if ($completeItems > $total_items) {
2483
            $completeItems = $total_items;
2484
        }
2485
        $percentage = 0;
2486
        if ($mode == '%') {
2487
            if ($total_items > 0) {
2488
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2489
            }
2490
            $percentage = number_format($percentage, 0);
2491
            $text = '%';
2492
        } elseif ($mode === 'abs') {
2493
            $percentage = $completeItems;
2494
            $text = '/'.$total_items;
2495
        }
2496
2497
        return [
2498
            $percentage,
2499
            $text,
2500
        ];
2501
    }
2502
2503
    /**
2504
     * Gets the progress bar mode.
2505
     *
2506
     * @return string The progress bar mode attribute
2507
     */
2508
    public function get_progress_bar_mode()
2509
    {
2510
        if (!empty($this->progress_bar_mode)) {
2511
            return $this->progress_bar_mode;
2512
        }
2513
2514
        return '%';
2515
    }
2516
2517
    /**
2518
     * Gets the learnpath theme (remote or local).
2519
     *
2520
     * @return string Learnpath theme
2521
     */
2522
    public function get_theme()
2523
    {
2524
        if (!empty($this->theme)) {
2525
            return $this->theme;
2526
        }
2527
2528
        return '';
2529
    }
2530
2531
    /**
2532
     * Gets the learnpath session id.
2533
     *
2534
     * @return int
2535
     */
2536
    public function get_lp_session_id()
2537
    {
2538
        if (!empty($this->lp_session_id)) {
2539
            return (int) $this->lp_session_id;
2540
        }
2541
2542
        return 0;
2543
    }
2544
2545
    /**
2546
     * Gets the learnpath image.
2547
     *
2548
     * @return string Web URL of the LP image
2549
     */
2550
    public function get_preview_image()
2551
    {
2552
        if (!empty($this->preview_image)) {
2553
            return $this->preview_image;
2554
        }
2555
2556
        return '';
2557
    }
2558
2559
    /**
2560
     * @param string $size
2561
     * @param string $path_type
2562
     *
2563
     * @return bool|string
2564
     */
2565
    public function get_preview_image_path($size = null, $path_type = 'web')
2566
    {
2567
        $preview_image = $this->get_preview_image();
2568
        if (isset($preview_image) && !empty($preview_image)) {
2569
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2570
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2571
2572
            if (isset($size)) {
2573
                $info = pathinfo($preview_image);
2574
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2575
2576
                if (file_exists($image_sys_path.$image_custom_size)) {
2577
                    if ($path_type == 'web') {
2578
                        return $image_path.$image_custom_size;
2579
                    } else {
2580
                        return $image_sys_path.$image_custom_size;
2581
                    }
2582
                }
2583
            } else {
2584
                if ($path_type == 'web') {
2585
                    return $image_path.$preview_image;
2586
                } else {
2587
                    return $image_sys_path.$preview_image;
2588
                }
2589
            }
2590
        }
2591
2592
        return false;
2593
    }
2594
2595
    /**
2596
     * Gets the learnpath author.
2597
     *
2598
     * @return string LP's author
2599
     */
2600
    public function get_author()
2601
    {
2602
        if (!empty($this->author)) {
2603
            return $this->author;
2604
        }
2605
2606
        return '';
2607
    }
2608
2609
    /**
2610
     * Gets hide table of contents.
2611
     *
2612
     * @return int
2613
     */
2614
    public function getHideTableOfContents()
2615
    {
2616
        return (int) $this->hide_toc_frame;
2617
    }
2618
2619
    /**
2620
     * Generate a new prerequisites string for a given item. If this item was a sco and
2621
     * its prerequisites were strings (instead of IDs), then transform those strings into
2622
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2623
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2624
     * same rule as the scormExport() method.
2625
     *
2626
     * @param int $item_id Item ID
2627
     *
2628
     * @return string Prerequisites string ready for the export as SCORM
2629
     */
2630
    public function get_scorm_prereq_string($item_id)
2631
    {
2632
        if ($this->debug > 0) {
2633
            error_log('In learnpath::get_scorm_prereq_string()');
2634
        }
2635
        if (!is_object($this->items[$item_id])) {
2636
            return false;
2637
        }
2638
        /** @var learnpathItem $oItem */
2639
        $oItem = $this->items[$item_id];
2640
        $prereq = $oItem->get_prereq_string();
2641
2642
        if (empty($prereq)) {
2643
            return '';
2644
        }
2645
        if (preg_match('/^\d+$/', $prereq) &&
2646
            isset($this->items[$prereq]) &&
2647
            is_object($this->items[$prereq])
2648
        ) {
2649
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2650
            // then simply return it (with the ITEM_ prefix).
2651
            //return 'ITEM_' . $prereq;
2652
            return $this->items[$prereq]->ref;
2653
        } else {
2654
            if (isset($this->refs_list[$prereq])) {
2655
                // It's a simple string item from which the ID can be found in the refs list,
2656
                // so we can transform it directly to an ID for export.
2657
                return $this->items[$this->refs_list[$prereq]]->ref;
2658
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2659
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2660
            } else {
2661
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2662
                // and replace them, one by one, by the internal IDs (chamilo db)
2663
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2664
                // by a space as well.
2665
                $find = [
2666
                    '&',
2667
                    '|',
2668
                    '~',
2669
                    '=',
2670
                    '<>',
2671
                    '{',
2672
                    '}',
2673
                    '*',
2674
                    '(',
2675
                    ')',
2676
                ];
2677
                $replace = [
2678
                    ' ',
2679
                    ' ',
2680
                    ' ',
2681
                    ' ',
2682
                    ' ',
2683
                    ' ',
2684
                    ' ',
2685
                    ' ',
2686
                    ' ',
2687
                    ' ',
2688
                ];
2689
                $prereq_mod = str_replace($find, $replace, $prereq);
2690
                $ids = explode(' ', $prereq_mod);
2691
                foreach ($ids as $id) {
2692
                    $id = trim($id);
2693
                    if (isset($this->refs_list[$id])) {
2694
                        $prereq = preg_replace(
2695
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2696
                            'ITEM_'.$this->refs_list[$id],
2697
                            $prereq
2698
                        );
2699
                    }
2700
                }
2701
2702
                return $prereq;
2703
            }
2704
        }
2705
    }
2706
2707
    /**
2708
     * Returns the XML DOM document's node.
2709
     *
2710
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2711
     * @param string   $id       The identifier to look for
2712
     *
2713
     * @return mixed The reference to the element found with that identifier. False if not found
2714
     */
2715
    public function get_scorm_xml_node(&$children, $id)
2716
    {
2717
        for ($i = 0; $i < $children->length; $i++) {
2718
            $item_temp = $children->item($i);
2719
            if ($item_temp->nodeName == 'item') {
2720
                if ($item_temp->getAttribute('identifier') == $id) {
2721
                    return $item_temp;
2722
                }
2723
            }
2724
            $subchildren = $item_temp->childNodes;
2725
            if ($subchildren && $subchildren->length > 0) {
2726
                $val = $this->get_scorm_xml_node($subchildren, $id);
2727
                if (is_object($val)) {
2728
                    return $val;
2729
                }
2730
            }
2731
        }
2732
2733
        return false;
2734
    }
2735
2736
    /**
2737
     * Return the number of interactions for the given learnpath Item View ID.
2738
     * This method can be used as static.
2739
     *
2740
     * @param int $lp_iv_id  Item View ID
2741
     * @param int $course_id course id
2742
     *
2743
     * @return int
2744
     */
2745
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2746
    {
2747
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2748
        $lp_iv_id = (int) $lp_iv_id;
2749
        $course_id = (int) $course_id;
2750
2751
        $sql = "SELECT count(*) FROM $table
2752
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2753
        $res = Database::query($sql);
2754
        $num = 0;
2755
        if (Database::num_rows($res)) {
2756
            $row = Database::fetch_array($res);
2757
            $num = $row[0];
2758
        }
2759
2760
        return $num;
2761
    }
2762
2763
    /**
2764
     * Return the interactions as an array for the given lp_iv_id.
2765
     * This method can be used as static.
2766
     *
2767
     * @param int $lp_iv_id Learnpath Item View ID
2768
     *
2769
     * @return array
2770
     *
2771
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2772
     */
2773
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2774
    {
2775
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2776
        $list = [];
2777
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2778
        $lp_iv_id = (int) $lp_iv_id;
2779
2780
        if (empty($lp_iv_id) || empty($course_id)) {
2781
            return [];
2782
        }
2783
2784
        $sql = "SELECT * FROM $table
2785
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2786
                ORDER BY order_id ASC";
2787
        $res = Database::query($sql);
2788
        $num = Database::num_rows($res);
2789
        if ($num > 0) {
2790
            $list[] = [
2791
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2792
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2793
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2794
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2795
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2796
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2797
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2798
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2799
                'student_response_formatted' => '',
2800
            ];
2801
            while ($row = Database::fetch_array($res)) {
2802
                $studentResponseFormatted = urldecode($row['student_response']);
2803
                $content_student_response = explode('__|', $studentResponseFormatted);
2804
                if (count($content_student_response) > 0) {
2805
                    if (count($content_student_response) >= 3) {
2806
                        // Pop the element off the end of array.
2807
                        array_pop($content_student_response);
2808
                    }
2809
                    $studentResponseFormatted = implode(',', $content_student_response);
2810
                }
2811
2812
                $list[] = [
2813
                    'order_id' => $row['order_id'] + 1,
2814
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2815
                    'type' => $row['interaction_type'],
2816
                    'time' => $row['completion_time'],
2817
                    'correct_responses' => '', // Hide correct responses from students.
2818
                    'student_response' => $row['student_response'],
2819
                    'result' => $row['result'],
2820
                    'latency' => $row['latency'],
2821
                    'student_response_formatted' => $studentResponseFormatted,
2822
                ];
2823
            }
2824
        }
2825
2826
        return $list;
2827
    }
2828
2829
    /**
2830
     * Return the number of objectives for the given learnpath Item View ID.
2831
     * This method can be used as static.
2832
     *
2833
     * @param int $lp_iv_id  Item View ID
2834
     * @param int $course_id Course ID
2835
     *
2836
     * @return int Number of objectives
2837
     */
2838
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2839
    {
2840
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2841
        $course_id = (int) $course_id;
2842
        $lp_iv_id = (int) $lp_iv_id;
2843
        $sql = "SELECT count(*) FROM $table
2844
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2845
        //@todo seems that this always returns 0
2846
        $res = Database::query($sql);
2847
        $num = 0;
2848
        if (Database::num_rows($res)) {
2849
            $row = Database::fetch_array($res);
2850
            $num = $row[0];
2851
        }
2852
2853
        return $num;
2854
    }
2855
2856
    /**
2857
     * Return the objectives as an array for the given lp_iv_id.
2858
     * This method can be used as static.
2859
     *
2860
     * @param int $lpItemViewId Learnpath Item View ID
2861
     * @param int $course_id
2862
     *
2863
     * @return array
2864
     *
2865
     * @todo    Translate labels
2866
     */
2867
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2868
    {
2869
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2870
        $lpItemViewId = (int) $lpItemViewId;
2871
2872
        if (empty($course_id) || empty($lpItemViewId)) {
2873
            return [];
2874
        }
2875
2876
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2877
        $sql = "SELECT * FROM $table
2878
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2879
                ORDER BY order_id ASC";
2880
        $res = Database::query($sql);
2881
        $num = Database::num_rows($res);
2882
        $list = [];
2883
        if ($num > 0) {
2884
            $list[] = [
2885
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2886
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
2887
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
2888
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
2889
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
2890
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
2891
            ];
2892
            while ($row = Database::fetch_array($res)) {
2893
                $list[] = [
2894
                    'order_id' => $row['order_id'] + 1,
2895
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
2896
                    'score_raw' => $row['score_raw'],
2897
                    'score_max' => $row['score_max'],
2898
                    'score_min' => $row['score_min'],
2899
                    'status' => $row['status'],
2900
                ];
2901
            }
2902
        }
2903
2904
        return $list;
2905
    }
2906
2907
    /**
2908
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
2909
     * used by get_html_toc() to be ready to display.
2910
     *
2911
     * @return array TOC as a table with 4 elements per row: title, link, status and level
2912
     */
2913
    public function get_toc()
2914
    {
2915
        $toc = [];
2916
        foreach ($this->ordered_items as $item_id) {
2917
            // TODO: Change this link generation and use new function instead.
2918
            $toc[] = [
2919
                'id' => $item_id,
2920
                'title' => $this->items[$item_id]->get_title(),
2921
                'status' => $this->items[$item_id]->get_status(),
2922
                'level' => $this->items[$item_id]->get_level(),
2923
                'type' => $this->items[$item_id]->get_type(),
2924
                'description' => $this->items[$item_id]->get_description(),
2925
                'path' => $this->items[$item_id]->get_path(),
2926
                'parent' => $this->items[$item_id]->get_parent(),
2927
            ];
2928
        }
2929
2930
        return $toc;
2931
    }
2932
2933
    /**
2934
     * Generate and return the table of contents for this learnpath. The JS
2935
     * table returned is used inside of scorm_api.php.
2936
     *
2937
     * @param string $varname
2938
     *
2939
     * @return string A JS array variable construction
2940
     */
2941
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
2942
    {
2943
        $toc = $varname.' = new Array();';
2944
        foreach ($this->ordered_items as $item_id) {
2945
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
2946
        }
2947
2948
        return $toc;
2949
    }
2950
2951
    /**
2952
     * Gets the learning path type.
2953
     *
2954
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
2955
     *
2956
     * @return mixed Type ID or name, depending on the parameter
2957
     */
2958
    public function get_type($get_name = false)
2959
    {
2960
        $res = false;
2961
        if (!empty($this->type) && (!$get_name)) {
2962
            $res = $this->type;
2963
        }
2964
2965
        return $res;
2966
    }
2967
2968
    /**
2969
     * Gets the learning path type as static method.
2970
     *
2971
     * @param int $lp_id
2972
     *
2973
     * @return mixed Type ID or name, depending on the parameter
2974
     */
2975
    public static function get_type_static($lp_id = 0)
2976
    {
2977
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
2978
        $lp_id = (int) $lp_id;
2979
        $sql = "SELECT lp_type FROM $tbl_lp
2980
                WHERE iid = $lp_id";
2981
        $res = Database::query($sql);
2982
        if ($res === false) {
2983
            return null;
2984
        }
2985
        if (Database::num_rows($res) <= 0) {
2986
            return null;
2987
        }
2988
        $row = Database::fetch_array($res);
2989
2990
        return $row['lp_type'];
2991
    }
2992
2993
    /**
2994
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
2995
     * This method can be used as abstract and is recursive.
2996
     *
2997
     * @param int $lp        Learnpath ID
2998
     * @param int $parent    Parent ID of the items to look for
2999
     * @param int $course_id
3000
     *
3001
     * @return array Ordered list of item IDs (empty array on error)
3002
     */
3003
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3004
    {
3005
        if (empty($course_id)) {
3006
            $course_id = api_get_course_int_id();
3007
        } else {
3008
            $course_id = (int) $course_id;
3009
        }
3010
        $list = [];
3011
3012
        if (empty($lp)) {
3013
            return $list;
3014
        }
3015
3016
        $lp = (int) $lp;
3017
        $parent = (int) $parent;
3018
3019
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3020
        $sql = "SELECT iid FROM $tbl_lp_item
3021
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3022
                ORDER BY display_order";
3023
3024
        $res = Database::query($sql);
3025
        while ($row = Database::fetch_array($res)) {
3026
            $sublist = self::get_flat_ordered_items_list(
3027
                $lp,
3028
                $row['iid'],
3029
                $course_id
3030
            );
3031
            $list[] = $row['iid'];
3032
            foreach ($sublist as $item) {
3033
                $list[] = $item;
3034
            }
3035
        }
3036
3037
        return $list;
3038
    }
3039
3040
    /**
3041
     * @return array
3042
     */
3043
    public static function getChapterTypes()
3044
    {
3045
        return [
3046
            'dir',
3047
        ];
3048
    }
3049
3050
    /**
3051
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3052
     *
3053
     * @param $tree
3054
     *
3055
     * @return array HTML TOC ready to display
3056
     */
3057
    public function getParentToc($tree)
3058
    {
3059
        if (empty($tree)) {
3060
            $tree = $this->get_toc();
3061
        }
3062
        $dirTypes = self::getChapterTypes();
3063
        $myCurrentId = $this->get_current_item_id();
3064
        $listParent = [];
3065
        $listChildren = [];
3066
        $listNotParent = [];
3067
        $list = [];
3068
        foreach ($tree as $subtree) {
3069
            if (in_array($subtree['type'], $dirTypes)) {
3070
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3071
                $subtree['children'] = $listChildren;
3072
                if (!empty($subtree['children'])) {
3073
                    foreach ($subtree['children'] as $subItem) {
3074
                        if ($subItem['id'] == $this->current) {
3075
                            $subtree['parent_current'] = 'in';
3076
                            $subtree['current'] = 'on';
3077
                        }
3078
                    }
3079
                }
3080
                $listParent[] = $subtree;
3081
            }
3082
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3083
                $classStatus = [
3084
                    'not attempted' => 'scorm_not_attempted',
3085
                    'incomplete' => 'scorm_not_attempted',
3086
                    'failed' => 'scorm_failed',
3087
                    'completed' => 'scorm_completed',
3088
                    'passed' => 'scorm_completed',
3089
                    'succeeded' => 'scorm_completed',
3090
                    'browsed' => 'scorm_completed',
3091
                ];
3092
3093
                if (isset($classStatus[$subtree['status']])) {
3094
                    $cssStatus = $classStatus[$subtree['status']];
3095
                }
3096
3097
                $title = Security::remove_XSS($subtree['title']);
3098
                unset($subtree['title']);
3099
3100
                if (empty($title)) {
3101
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3102
                }
3103
                $classStyle = null;
3104
                if ($subtree['id'] == $this->current) {
3105
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3106
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3107
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3108
                }
3109
                $subtree['title'] = $title;
3110
                $subtree['class'] = $classStyle.' '.$cssStatus;
3111
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3112
                $subtree['current_id'] = $myCurrentId;
3113
                $listNotParent[] = $subtree;
3114
            }
3115
        }
3116
3117
        $list['are_parents'] = $listParent;
3118
        $list['not_parents'] = $listNotParent;
3119
3120
        return $list;
3121
    }
3122
3123
    /**
3124
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3125
     *
3126
     * @param array $tree
3127
     * @param int   $id
3128
     * @param bool  $parent
3129
     *
3130
     * @return array HTML TOC ready to display
3131
     */
3132
    public function getChildrenToc($tree, $id, $parent = true)
3133
    {
3134
        if (empty($tree)) {
3135
            $tree = $this->get_toc();
3136
        }
3137
3138
        $dirTypes = self::getChapterTypes();
3139
        $currentItemId = $this->get_current_item_id();
3140
        $list = [];
3141
        $classStatus = [
3142
            'not attempted' => 'scorm_not_attempted',
3143
            'incomplete' => 'scorm_not_attempted',
3144
            'failed' => 'scorm_failed',
3145
            'completed' => 'scorm_completed',
3146
            'passed' => 'scorm_completed',
3147
            'succeeded' => 'scorm_completed',
3148
            'browsed' => 'scorm_completed',
3149
        ];
3150
3151
        foreach ($tree as $subtree) {
3152
            $subtree['tree'] = null;
3153
3154
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3155
                if ($subtree['id'] == $this->current) {
3156
                    $subtree['current'] = 'active';
3157
                } else {
3158
                    $subtree['current'] = null;
3159
                }
3160
                if (isset($classStatus[$subtree['status']])) {
3161
                    $cssStatus = $classStatus[$subtree['status']];
3162
                }
3163
3164
                $title = Security::remove_XSS($subtree['title']);
3165
                unset($subtree['title']);
3166
                if (empty($title)) {
3167
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3168
                }
3169
3170
                $classStyle = null;
3171
                if ($subtree['id'] == $this->current) {
3172
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3173
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3174
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3175
                }
3176
3177
                if (in_array($subtree['type'], $dirTypes)) {
3178
                    $subtree['title'] = stripslashes($title);
3179
                } else {
3180
                    $subtree['title'] = $title;
3181
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3182
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3183
                    $subtree['current_id'] = $currentItemId;
3184
                }
3185
                $list[] = $subtree;
3186
            }
3187
        }
3188
3189
        return $list;
3190
    }
3191
3192
    /**
3193
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3194
     *
3195
     * @param array $toc_list
3196
     *
3197
     * @return array HTML TOC ready to display
3198
     */
3199
    public function getListArrayToc($toc_list = [])
3200
    {
3201
        if (empty($toc_list)) {
3202
            $toc_list = $this->get_toc();
3203
        }
3204
        // Temporary variables.
3205
        $currentItemId = $this->get_current_item_id();
3206
        $list = [];
3207
        $arrayList = [];
3208
        $classStatus = [
3209
            'not attempted' => 'scorm_not_attempted',
3210
            'incomplete' => 'scorm_not_attempted',
3211
            'failed' => 'scorm_failed',
3212
            'completed' => 'scorm_completed',
3213
            'passed' => 'scorm_completed',
3214
            'succeeded' => 'scorm_completed',
3215
            'browsed' => 'scorm_completed',
3216
        ];
3217
3218
        foreach ($toc_list as $item) {
3219
            $list['id'] = $item['id'];
3220
            $list['status'] = $item['status'];
3221
            $cssStatus = null;
3222
3223
            if (isset($classStatus[$item['status']])) {
3224
                $cssStatus = $classStatus[$item['status']];
3225
            }
3226
3227
            $classStyle = ' ';
3228
            $dirTypes = self::getChapterTypes();
3229
3230
            if (in_array($item['type'], $dirTypes)) {
3231
                $classStyle = 'scorm_item_section ';
3232
            }
3233
            if ($item['id'] == $this->current) {
3234
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3235
            } elseif (!in_array($item['type'], $dirTypes)) {
3236
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3237
            }
3238
            $title = $item['title'];
3239
            if (empty($title)) {
3240
                $title = self::rl_get_resource_name(
3241
                    api_get_course_id(),
3242
                    $this->get_id(),
3243
                    $item['id']
3244
                );
3245
            }
3246
            $title = Security::remove_XSS($item['title']);
3247
3248
            if (empty($item['description'])) {
3249
                $list['description'] = $title;
3250
            } else {
3251
                $list['description'] = $item['description'];
3252
            }
3253
3254
            $list['class'] = $classStyle.' '.$cssStatus;
3255
            $list['level'] = $item['level'];
3256
            $list['type'] = $item['type'];
3257
3258
            if (in_array($item['type'], $dirTypes)) {
3259
                $list['css_level'] = 'level_'.$item['level'];
3260
            } else {
3261
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3262
            }
3263
3264
            if (in_array($item['type'], $dirTypes)) {
3265
                $list['title'] = stripslashes($title);
3266
            } else {
3267
                $list['title'] = stripslashes($title);
3268
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3269
                $list['current_id'] = $currentItemId;
3270
            }
3271
            $arrayList[] = $list;
3272
        }
3273
3274
        return $arrayList;
3275
    }
3276
3277
    /**
3278
     * Returns an HTML-formatted string ready to display with teacher buttons
3279
     * in LP view menu.
3280
     *
3281
     * @return string HTML TOC ready to display
3282
     */
3283
    public function get_teacher_toc_buttons()
3284
    {
3285
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3286
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3287
        $html = '';
3288
        if ($isAllow && $hideIcons == false) {
3289
            if ($this->get_lp_session_id() == api_get_session_id()) {
3290
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3291
                $html .= '<div class="btn-group">';
3292
                $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'>".
3293
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3294
                $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'>".
3295
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3296
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3297
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3298
                $html .= '</div>';
3299
                $html .= '</div>';
3300
            }
3301
        }
3302
3303
        return $html;
3304
    }
3305
3306
    /**
3307
     * Gets the learnpath maker name - generally the editor's name.
3308
     *
3309
     * @return string Learnpath maker name
3310
     */
3311
    public function get_maker()
3312
    {
3313
        if (!empty($this->maker)) {
3314
            return $this->maker;
3315
        }
3316
3317
        return '';
3318
    }
3319
3320
    /**
3321
     * Gets the learnpath name/title.
3322
     *
3323
     * @return string Learnpath name/title
3324
     */
3325
    public function get_name()
3326
    {
3327
        if (!empty($this->name)) {
3328
            return $this->name;
3329
        }
3330
3331
        return 'N/A';
3332
    }
3333
3334
    /**
3335
     * @return string
3336
     */
3337
    public function getNameNoTags()
3338
    {
3339
        return strip_tags($this->get_name());
3340
    }
3341
3342
    /**
3343
     * Gets a link to the resource from the present location, depending on item ID.
3344
     *
3345
     * @param string $type         Type of link expected
3346
     * @param int    $item_id      Learnpath item ID
3347
     * @param bool   $provided_toc
3348
     *
3349
     * @return string $provided_toc Link to the lp_item resource
3350
     */
3351
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3352
    {
3353
        $course_id = $this->get_course_int_id();
3354
        $item_id = (int) $item_id;
3355
3356
        if (empty($item_id)) {
3357
            $item_id = $this->get_current_item_id();
3358
3359
            if (empty($item_id)) {
3360
                //still empty, this means there was no item_id given and we are not in an object context or
3361
                //the object property is empty, return empty link
3362
                $this->first();
3363
3364
                return '';
3365
            }
3366
        }
3367
3368
        $file = '';
3369
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3370
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3371
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3372
3373
        $sql = "SELECT
3374
                    l.lp_type as ltype,
3375
                    l.path as lpath,
3376
                    li.item_type as litype,
3377
                    li.path as lipath,
3378
                    li.parameters as liparams
3379
        		FROM $lp_table l
3380
                INNER JOIN $lp_item_table li
3381
                ON (li.lp_id = l.iid)
3382
        		WHERE
3383
        		    li.iid = $item_id
3384
        		";
3385
        $res = Database::query($sql);
3386
        if (Database::num_rows($res) > 0) {
3387
            $row = Database::fetch_array($res);
3388
            $lp_type = $row['ltype'];
3389
            $lp_path = $row['lpath'];
3390
            $lp_item_type = $row['litype'];
3391
            $lp_item_path = $row['lipath'];
3392
            $lp_item_params = $row['liparams'];
3393
3394
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3395
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3396
            }
3397
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3398
            if ($type === 'http') {
3399
                //web path
3400
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3401
            } else {
3402
                $course_path = $sys_course_path; //system path
3403
            }
3404
3405
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3406
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3407
            if (in_array(
3408
                $lp_item_type,
3409
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3410
            )
3411
            ) {
3412
                $lp_type = 1;
3413
            }
3414
3415
            // Now go through the specific cases to get the end of the path
3416
            // @todo Use constants instead of int values.
3417
            switch ($lp_type) {
3418
                case 1:
3419
                    $file = self::rl_get_resource_link_for_learnpath(
3420
                        $course_id,
3421
                        $this->get_id(),
3422
                        $item_id,
3423
                        $this->get_view_id()
3424
                    );
3425
                    switch ($lp_item_type) {
3426
                        case 'document':
3427
                            // Shows a button to download the file instead of just downloading the file directly.
3428
                            $documentPathInfo = pathinfo($file);
3429
                            if (isset($documentPathInfo['extension'])) {
3430
                                $parsed = parse_url($documentPathInfo['extension']);
3431
                                if (isset($parsed['path'])) {
3432
                                    $extension = $parsed['path'];
3433
                                    $extensionsToDownload = [
3434
                                        'zip',
3435
                                        'ppt',
3436
                                        'pptx',
3437
                                        'ods',
3438
                                        'xlsx',
3439
                                        'xls',
3440
                                        'csv',
3441
                                        'doc',
3442
                                        'docx',
3443
                                        'dot',
3444
                                    ];
3445
3446
                                    if (in_array($extension, $extensionsToDownload)) {
3447
                                        $file = api_get_path(WEB_CODE_PATH).
3448
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3449
                                    }
3450
                                }
3451
                            }
3452
                            break;
3453
                        case 'dir':
3454
                            $file = 'lp_content.php?type=dir';
3455
                            break;
3456
                        case 'link':
3457
                            if (Link::is_youtube_link($file)) {
3458
                                $src = Link::get_youtube_video_id($file);
3459
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3460
                            } elseif (Link::isVimeoLink($file)) {
3461
                                $src = Link::getVimeoLinkId($file);
3462
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3463
                            } else {
3464
                                // If the current site is HTTPS and the link is
3465
                                // HTTP, browsers will refuse opening the link
3466
                                $urlId = api_get_current_access_url_id();
3467
                                $url = api_get_access_url($urlId, false);
3468
                                $protocol = substr($url['url'], 0, 5);
3469
                                if ($protocol === 'https') {
3470
                                    $linkProtocol = substr($file, 0, 5);
3471
                                    if ($linkProtocol === 'http:') {
3472
                                        //this is the special intervention case
3473
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3474
                                    }
3475
                                }
3476
                            }
3477
                            break;
3478
                        case 'quiz':
3479
                            // Check how much attempts of a exercise exits in lp
3480
                            $lp_item_id = $this->get_current_item_id();
3481
                            $lp_view_id = $this->get_view_id();
3482
3483
                            $prevent_reinit = null;
3484
                            if (isset($this->items[$this->current])) {
3485
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3486
                            }
3487
3488
                            if (empty($provided_toc)) {
3489
                                if ($this->debug > 0) {
3490
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3491
                                }
3492
                                $list = $this->get_toc();
3493
                            } else {
3494
                                if ($this->debug > 0) {
3495
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3496
                                }
3497
                                $list = $provided_toc;
3498
                            }
3499
3500
                            $type_quiz = false;
3501
                            foreach ($list as $toc) {
3502
                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
3503
                                    $type_quiz = true;
3504
                                }
3505
                            }
3506
3507
                            if ($type_quiz) {
3508
                                $lp_item_id = (int) $lp_item_id;
3509
                                $lp_view_id = (int) $lp_view_id;
3510
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3511
                                        WHERE
3512
                                            c_id = $course_id AND
3513
                                            lp_item_id='".$lp_item_id."' AND
3514
                                            lp_view_id ='".$lp_view_id."' AND
3515
                                            status='completed'";
3516
                                $result = Database::query($sql);
3517
                                $row_count = Database:: fetch_row($result);
3518
                                $count_item_view = (int) $row_count[0];
3519
                                $not_multiple_attempt = 0;
3520
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3521
                                    $not_multiple_attempt = 1;
3522
                                }
3523
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3524
                            }
3525
                            break;
3526
                    }
3527
3528
                    $tmp_array = explode('/', $file);
3529
                    $document_name = $tmp_array[count($tmp_array) - 1];
3530
                    if (strpos($document_name, '_DELETED_')) {
3531
                        $file = 'blank.php?error=document_deleted';
3532
                    }
3533
                    break;
3534
                case 2:
3535
                    if ($this->debug > 2) {
3536
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3537
                    }
3538
3539
                    if ($lp_item_type != 'dir') {
3540
                        // Quite complex here:
3541
                        // We want to make sure 'http://' (and similar) links can
3542
                        // be loaded as is (withouth the Chamilo path in front) but
3543
                        // some contents use this form: resource.htm?resource=http://blablabla
3544
                        // which means we have to find a protocol at the path's start, otherwise
3545
                        // it should not be considered as an external URL.
3546
                        // if ($this->prerequisites_match($item_id)) {
3547
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3548
                            if ($this->debug > 2) {
3549
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3550
                            }
3551
                            // Distant url, return as is.
3552
                            $file = $lp_item_path;
3553
                        } else {
3554
                            if ($this->debug > 2) {
3555
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3556
                            }
3557
                            // Prevent getting untranslatable urls.
3558
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3559
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3560
                            // Prepare the path.
3561
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3562
                            // TODO: Fix this for urls with protocol header.
3563
                            $file = str_replace('//', '/', $file);
3564
                            $file = str_replace(':/', '://', $file);
3565
                            if (substr($lp_path, -1) == '/') {
3566
                                $lp_path = substr($lp_path, 0, -1);
3567
                            }
3568
3569
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3570
                                // if file not found.
3571
                                $decoded = html_entity_decode($lp_item_path);
3572
                                list($decoded) = explode('?', $decoded);
3573
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3574
                                    $file = self::rl_get_resource_link_for_learnpath(
3575
                                        $course_id,
3576
                                        $this->get_id(),
3577
                                        $item_id,
3578
                                        $this->get_view_id()
3579
                                    );
3580
                                    if (empty($file)) {
3581
                                        $file = 'blank.php?error=document_not_found';
3582
                                    } else {
3583
                                        $tmp_array = explode('/', $file);
3584
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3585
                                        if (strpos($document_name, '_DELETED_')) {
3586
                                            $file = 'blank.php?error=document_deleted';
3587
                                        } else {
3588
                                            $file = 'blank.php?error=document_not_found';
3589
                                        }
3590
                                    }
3591
                                } else {
3592
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3593
                                }
3594
                            }
3595
                        }
3596
3597
                        // We want to use parameters if they were defined in the imsmanifest
3598
                        if (strpos($file, 'blank.php') === false) {
3599
                            $lp_item_params = ltrim($lp_item_params, '?');
3600
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3601
                        }
3602
                    } else {
3603
                        $file = 'lp_content.php?type=dir';
3604
                    }
3605
                    break;
3606
                case 3:
3607
                    if ($this->debug > 2) {
3608
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3609
                    }
3610
                    // Formatting AICC HACP append URL.
3611
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3612
                    if (!empty($lp_item_params)) {
3613
                        $aicc_append .= $lp_item_params.'&';
3614
                    }
3615
                    if ($lp_item_type != 'dir') {
3616
                        // Quite complex here:
3617
                        // We want to make sure 'http://' (and similar) links can
3618
                        // be loaded as is (withouth the Chamilo path in front) but
3619
                        // some contents use this form: resource.htm?resource=http://blablabla
3620
                        // which means we have to find a protocol at the path's start, otherwise
3621
                        // it should not be considered as an external URL.
3622
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3623
                            if ($this->debug > 2) {
3624
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3625
                            }
3626
                            // Distant url, return as is.
3627
                            $file = $lp_item_path;
3628
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3629
                            /*
3630
                            if (stristr($file,'<servername>') !== false) {
3631
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3632
                            }
3633
                            */
3634
                            if (stripos($file, '<servername>') !== false) {
3635
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3636
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3637
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3638
                            }
3639
3640
                            $file .= $aicc_append;
3641
                        } else {
3642
                            if ($this->debug > 2) {
3643
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3644
                            }
3645
                            // Prevent getting untranslatable urls.
3646
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3647
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3648
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3649
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3650
                            // TODO: Fix this for urls with protocol header.
3651
                            $file = str_replace('//', '/', $file);
3652
                            $file = str_replace(':/', '://', $file);
3653
                            $file .= $aicc_append;
3654
                        }
3655
                    } else {
3656
                        $file = 'lp_content.php?type=dir';
3657
                    }
3658
                    break;
3659
                case 4:
3660
                    break;
3661
                default:
3662
                    break;
3663
            }
3664
            // Replace &amp; by & because &amp; will break URL with params
3665
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3666
        }
3667
        if ($this->debug > 2) {
3668
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3669
        }
3670
3671
        return $file;
3672
    }
3673
3674
    /**
3675
     * Gets the latest usable view or generate a new one.
3676
     *
3677
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3678
     *
3679
     * @return int DB lp_view id
3680
     */
3681
    public function get_view($attempt_num = 0)
3682
    {
3683
        $search = '';
3684
        // Use $attempt_num to enable multi-views management (disabled so far).
3685
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3686
            $search = 'AND view_count = '.$attempt_num;
3687
        }
3688
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3689
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3690
3691
        $course_id = api_get_course_int_id();
3692
        $sessionId = api_get_session_id();
3693
3694
        $sql = "SELECT iid, view_count FROM $lp_view_table
3695
        		WHERE
3696
        		    c_id = $course_id AND
3697
        		    lp_id = ".$this->get_id()." AND
3698
        		    user_id = ".$this->get_user_id()." AND
3699
        		    session_id = $sessionId
3700
        		    $search
3701
                ORDER BY view_count DESC";
3702
        $res = Database::query($sql);
3703
        if (Database::num_rows($res) > 0) {
3704
            $row = Database::fetch_array($res);
3705
            $this->lp_view_id = $row['iid'];
3706
        } elseif (!api_is_invitee()) {
3707
            // There is no database record, create one.
3708
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3709
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3710
            Database::query($sql);
3711
            $id = Database::insert_id();
3712
            $this->lp_view_id = $id;
3713
3714
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3715
            Database::query($sql);
3716
        }
3717
3718
        return $this->lp_view_id;
3719
    }
3720
3721
    /**
3722
     * Gets the current view id.
3723
     *
3724
     * @return int View ID (from lp_view)
3725
     */
3726
    public function get_view_id()
3727
    {
3728
        if (!empty($this->lp_view_id)) {
3729
            return (int) $this->lp_view_id;
3730
        }
3731
3732
        return 0;
3733
    }
3734
3735
    /**
3736
     * Gets the update queue.
3737
     *
3738
     * @return array Array containing IDs of items to be updated by JavaScript
3739
     */
3740
    public function get_update_queue()
3741
    {
3742
        return $this->update_queue;
3743
    }
3744
3745
    /**
3746
     * Gets the user ID.
3747
     *
3748
     * @return int User ID
3749
     */
3750
    public function get_user_id()
3751
    {
3752
        if (!empty($this->user_id)) {
3753
            return (int) $this->user_id;
3754
        }
3755
3756
        return false;
3757
    }
3758
3759
    /**
3760
     * Checks if any of the items has an audio element attached.
3761
     *
3762
     * @return bool True or false
3763
     */
3764
    public function has_audio()
3765
    {
3766
        $has = false;
3767
        foreach ($this->items as $i => $item) {
3768
            if (!empty($this->items[$i]->audio)) {
3769
                $has = true;
3770
                break;
3771
            }
3772
        }
3773
3774
        return $has;
3775
    }
3776
3777
    /**
3778
     * Moves an item up and down at its level.
3779
     *
3780
     * @param int    $id        Item to move up and down
3781
     * @param string $direction Direction 'up' or 'down'
3782
     *
3783
     * @return bool|int
3784
     */
3785
    public function move_item($id, $direction)
3786
    {
3787
        $course_id = api_get_course_int_id();
3788
        if (empty($id) || empty($direction)) {
3789
            return false;
3790
        }
3791
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3792
        $sql_sel = "SELECT *
3793
                    FROM $tbl_lp_item
3794
                    WHERE
3795
                        iid = $id
3796
                    ";
3797
        $res_sel = Database::query($sql_sel);
3798
        // Check if elem exists.
3799
        if (Database::num_rows($res_sel) < 1) {
3800
            return false;
3801
        }
3802
        // Gather data.
3803
        $row = Database::fetch_array($res_sel);
3804
        $previous = $row['previous_item_id'];
3805
        $next = $row['next_item_id'];
3806
        $display = $row['display_order'];
3807
        $parent = $row['parent_item_id'];
3808
        $lp = $row['lp_id'];
3809
        // Update the item (switch with previous/next one).
3810
        switch ($direction) {
3811
            case 'up':
3812
                if ($display > 1) {
3813
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3814
                                 WHERE iid = $previous";
3815
                    $res_sel2 = Database::query($sql_sel2);
3816
                    if (Database::num_rows($res_sel2) < 1) {
3817
                        $previous_previous = 0;
3818
                    }
3819
                    // Gather data.
3820
                    $row2 = Database::fetch_array($res_sel2);
3821
                    $previous_previous = $row2['previous_item_id'];
3822
                    // Update previous_previous item (switch "next" with current).
3823
                    if ($previous_previous != 0) {
3824
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3825
                                        next_item_id = $id
3826
                                    WHERE iid = $previous_previous";
3827
                        Database::query($sql_upd2);
3828
                    }
3829
                    // Update previous item (switch with current).
3830
                    if ($previous != 0) {
3831
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3832
                                    next_item_id = $next,
3833
                                    previous_item_id = $id,
3834
                                    display_order = display_order +1
3835
                                    WHERE iid = $previous";
3836
                        Database::query($sql_upd2);
3837
                    }
3838
3839
                    // Update current item (switch with previous).
3840
                    if ($id != 0) {
3841
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3842
                                        next_item_id = $previous,
3843
                                        previous_item_id = $previous_previous,
3844
                                        display_order = display_order-1
3845
                                    WHERE c_id = ".$course_id." AND id = $id";
3846
                        Database::query($sql_upd2);
3847
                    }
3848
                    // Update next item (new previous item).
3849
                    if (!empty($next)) {
3850
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
3851
                                     WHERE iid = $next";
3852
                        Database::query($sql_upd2);
3853
                    }
3854
                    $display = $display - 1;
3855
                }
3856
                break;
3857
            case 'down':
3858
                if ($next != 0) {
3859
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3860
                                 WHERE iid = $next";
3861
                    $res_sel2 = Database::query($sql_sel2);
3862
                    if (Database::num_rows($res_sel2) < 1) {
3863
                        $next_next = 0;
3864
                    }
3865
                    // Gather data.
3866
                    $row2 = Database::fetch_array($res_sel2);
3867
                    $next_next = $row2['next_item_id'];
3868
                    // Update previous item (switch with current).
3869
                    if ($previous != 0) {
3870
                        $sql_upd2 = "UPDATE $tbl_lp_item
3871
                                     SET next_item_id = $next
3872
                                     WHERE iid = $previous";
3873
                        Database::query($sql_upd2);
3874
                    }
3875
                    // Update current item (switch with previous).
3876
                    if ($id != 0) {
3877
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3878
                                     previous_item_id = $next,
3879
                                     next_item_id = $next_next,
3880
                                     display_order = display_order + 1
3881
                                     WHERE iid = $id";
3882
                        Database::query($sql_upd2);
3883
                    }
3884
3885
                    // Update next item (new previous item).
3886
                    if ($next != 0) {
3887
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3888
                                     previous_item_id = $previous,
3889
                                     next_item_id = $id,
3890
                                     display_order = display_order-1
3891
                                     WHERE iid = $next";
3892
                        Database::query($sql_upd2);
3893
                    }
3894
3895
                    // Update next_next item (switch "previous" with current).
3896
                    if ($next_next != 0) {
3897
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3898
                                     previous_item_id = $id
3899
                                     WHERE iid = $next_next";
3900
                        Database::query($sql_upd2);
3901
                    }
3902
                    $display = $display + 1;
3903
                }
3904
                break;
3905
            default:
3906
                return false;
3907
        }
3908
3909
        return $display;
3910
    }
3911
3912
    /**
3913
     * Move a LP up (display_order).
3914
     *
3915
     * @param int $lp_id      Learnpath ID
3916
     * @param int $categoryId Category ID
3917
     *
3918
     * @return bool
3919
     */
3920
    public static function move_up($lp_id, $categoryId = 0)
3921
    {
3922
        $courseId = api_get_course_int_id();
3923
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3924
3925
        $categoryCondition = '';
3926
        if (!empty($categoryId)) {
3927
            $categoryId = (int) $categoryId;
3928
            $categoryCondition = " AND category_id = $categoryId";
3929
        }
3930
        $sql = "SELECT * FROM $lp_table
3931
                WHERE c_id = $courseId
3932
                $categoryCondition
3933
                ORDER BY display_order";
3934
        $res = Database::query($sql);
3935
        if ($res === false) {
3936
            return false;
3937
        }
3938
3939
        $lps = [];
3940
        $lp_order = [];
3941
        $num = Database::num_rows($res);
3942
        // First check the order is correct, globally (might be wrong because
3943
        // of versions < 1.8.4)
3944
        if ($num > 0) {
3945
            $i = 1;
3946
            while ($row = Database::fetch_array($res)) {
3947
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
3948
                    $sql = "UPDATE $lp_table SET display_order = $i
3949
                            WHERE iid = ".$row['iid'];
3950
                    Database::query($sql);
3951
                }
3952
                $row['display_order'] = $i;
3953
                $lps[$row['iid']] = $row;
3954
                $lp_order[$i] = $row['iid'];
3955
                $i++;
3956
            }
3957
        }
3958
        if ($num > 1) { // If there's only one element, no need to sort.
3959
            $order = $lps[$lp_id]['display_order'];
3960
            if ($order > 1) { // If it's the first element, no need to move up.
3961
                $sql = "UPDATE $lp_table SET display_order = $order
3962
                        WHERE iid = ".$lp_order[$order - 1];
3963
                Database::query($sql);
3964
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
3965
                        WHERE iid = $lp_id";
3966
                Database::query($sql);
3967
            }
3968
        }
3969
3970
        return true;
3971
    }
3972
3973
    /**
3974
     * Move a learnpath down (display_order).
3975
     *
3976
     * @param int $lp_id      Learnpath ID
3977
     * @param int $categoryId Category ID
3978
     *
3979
     * @return bool
3980
     */
3981
    public static function move_down($lp_id, $categoryId = 0)
3982
    {
3983
        $courseId = api_get_course_int_id();
3984
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3985
3986
        $categoryCondition = '';
3987
        if (!empty($categoryId)) {
3988
            $categoryId = (int) $categoryId;
3989
            $categoryCondition = " AND category_id = $categoryId";
3990
        }
3991
3992
        $sql = "SELECT * FROM $lp_table
3993
                WHERE c_id = $courseId
3994
                $categoryCondition
3995
                ORDER BY display_order";
3996
        $res = Database::query($sql);
3997
        if ($res === false) {
3998
            return false;
3999
        }
4000
        $lps = [];
4001
        $lp_order = [];
4002
        $num = Database::num_rows($res);
4003
        $max = 0;
4004
        // First check the order is correct, globally (might be wrong because
4005
        // of versions < 1.8.4).
4006
        if ($num > 0) {
4007
            $i = 1;
4008
            while ($row = Database::fetch_array($res)) {
4009
                $max = $i;
4010
                if ($row['display_order'] != $i) {
4011
                    // If we find a gap in the order, we need to fix it.
4012
                    $sql = "UPDATE $lp_table SET display_order = $i
4013
                              WHERE iid = ".$row['iid'];
4014
                    Database::query($sql);
4015
                }
4016
                $row['display_order'] = $i;
4017
                $lps[$row['iid']] = $row;
4018
                $lp_order[$i] = $row['iid'];
4019
                $i++;
4020
            }
4021
        }
4022
        if ($num > 1) { // If there's only one element, no need to sort.
4023
            $order = $lps[$lp_id]['display_order'];
4024
            if ($order < $max) { // If it's the first element, no need to move up.
4025
                $sql = "UPDATE $lp_table SET display_order = $order
4026
                        WHERE iid = ".$lp_order[$order + 1];
4027
                Database::query($sql);
4028
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4029
                        WHERE iid = $lp_id";
4030
                Database::query($sql);
4031
            }
4032
        }
4033
4034
        return true;
4035
    }
4036
4037
    /**
4038
     * Updates learnpath attributes to point to the next element
4039
     * The last part is similar to set_current_item but processing the other way around.
4040
     */
4041
    public function next()
4042
    {
4043
        if ($this->debug > 0) {
4044
            error_log('In learnpath::next()', 0);
4045
        }
4046
        $this->last = $this->get_current_item_id();
4047
        $this->items[$this->last]->save(
4048
            false,
4049
            $this->prerequisites_match($this->last)
4050
        );
4051
        $this->autocomplete_parents($this->last);
4052
        $new_index = $this->get_next_index();
4053
        if ($this->debug > 2) {
4054
            error_log('New index: '.$new_index, 0);
4055
        }
4056
        $this->index = $new_index;
4057
        if ($this->debug > 2) {
4058
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4059
        }
4060
        $this->current = $this->ordered_items[$new_index];
4061
        if ($this->debug > 2) {
4062
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4063
        }
4064
    }
4065
4066
    /**
4067
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4068
     * class, this might be redefined to allow several behaviours depending on the document type.
4069
     *
4070
     * @param int $id Resource ID
4071
     */
4072
    public function open($id)
4073
    {
4074
        // TODO:
4075
        // set the current resource attribute to this resource
4076
        // switch on element type (redefine in child class?)
4077
        // set status for this item to "opened"
4078
        // start timer
4079
        // initialise score
4080
        $this->index = 0; //or = the last item seen (see $this->last)
4081
    }
4082
4083
    /**
4084
     * Check that all prerequisites are fulfilled. Returns true and an
4085
     * empty string on success, returns false
4086
     * and the prerequisite string on error.
4087
     * This function is based on the rules for aicc_script language as
4088
     * described in the SCORM 1.2 CAM documentation page 108.
4089
     *
4090
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4091
     *
4092
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4093
     *              string otherwise
4094
     */
4095
    public function prerequisites_match($itemId = null)
4096
    {
4097
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4098
        if ($allow) {
4099
            if (api_is_allowed_to_edit() ||
4100
                api_is_platform_admin(true) ||
4101
                api_is_drh() ||
4102
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4103
            ) {
4104
                return true;
4105
            }
4106
        }
4107
4108
        $debug = $this->debug;
4109
        if ($debug > 0) {
4110
            error_log('In learnpath::prerequisites_match()');
4111
        }
4112
4113
        if (empty($itemId)) {
4114
            $itemId = $this->current;
4115
        }
4116
4117
        $currentItem = $this->getItem($itemId);
4118
4119
        if ($currentItem) {
4120
            if ($this->type == 2) {
4121
                // Getting prereq from scorm
4122
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4123
            } else {
4124
                $prereq_string = $currentItem->get_prereq_string();
4125
            }
4126
4127
            if (empty($prereq_string)) {
4128
                if ($debug > 0) {
4129
                    error_log('Found prereq_string is empty return true');
4130
                }
4131
4132
                return true;
4133
            }
4134
4135
            // Clean spaces.
4136
            $prereq_string = str_replace(' ', '', $prereq_string);
4137
            if ($debug > 0) {
4138
                error_log('Found prereq_string: '.$prereq_string, 0);
4139
            }
4140
4141
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4142
            $result = $currentItem->parse_prereq(
4143
                $prereq_string,
4144
                $this->items,
4145
                $this->refs_list,
4146
                $this->get_user_id()
4147
            );
4148
4149
            if ($result === false) {
4150
                $this->set_error_msg($currentItem->prereq_alert);
4151
            }
4152
        } else {
4153
            $result = true;
4154
            if ($debug > 1) {
4155
                error_log('$this->items['.$itemId.'] was not an object', 0);
4156
            }
4157
        }
4158
4159
        if ($debug > 1) {
4160
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4161
        }
4162
4163
        return $result;
4164
    }
4165
4166
    /**
4167
     * Updates learnpath attributes to point to the previous element
4168
     * The last part is similar to set_current_item but processing the other way around.
4169
     */
4170
    public function previous()
4171
    {
4172
        $this->last = $this->get_current_item_id();
4173
        $this->items[$this->last]->save(
4174
            false,
4175
            $this->prerequisites_match($this->last)
4176
        );
4177
        $this->autocomplete_parents($this->last);
4178
        $new_index = $this->get_previous_index();
4179
        $this->index = $new_index;
4180
        $this->current = $this->ordered_items[$new_index];
4181
    }
4182
4183
    /**
4184
     * Publishes a learnpath. This basically means show or hide the learnpath
4185
     * to normal users.
4186
     * Can be used as abstract.
4187
     *
4188
     * @param int $lp_id          Learnpath ID
4189
     * @param int $set_visibility New visibility
4190
     *
4191
     * @return bool
4192
     */
4193
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4194
    {
4195
        $action = 'visible';
4196
        if ($set_visibility != 1) {
4197
            $action = 'invisible';
4198
            self::toggle_publish($lp_id, 'i');
4199
        }
4200
4201
        return api_item_property_update(
4202
            api_get_course_info(),
4203
            TOOL_LEARNPATH,
4204
            $lp_id,
4205
            $action,
4206
            api_get_user_id()
4207
        );
4208
    }
4209
4210
    /**
4211
     * Publishes a learnpath category.
4212
     * This basically means show or hide the learnpath category to normal users.
4213
     *
4214
     * @param int $id
4215
     * @param int $visibility
4216
     *
4217
     * @throws \Doctrine\ORM\NonUniqueResultException
4218
     * @throws \Doctrine\ORM\ORMException
4219
     * @throws \Doctrine\ORM\OptimisticLockException
4220
     * @throws \Doctrine\ORM\TransactionRequiredException
4221
     *
4222
     * @return bool
4223
     */
4224
    public static function toggleCategoryVisibility($id, $visibility = 1)
4225
    {
4226
        $action = 'visible';
4227
4228
        if ($visibility != 1) {
4229
            $action = 'invisible';
4230
            $list = new LearnpathList(
4231
                api_get_user_id(),
4232
                null,
4233
                null,
4234
                null,
4235
                false,
4236
                $id
4237
            );
4238
4239
            $lpList = $list->get_flat_list();
4240
            foreach ($lpList as $lp) {
4241
                self::toggle_visibility($lp['iid'], 0);
4242
            }
4243
4244
            self::toggleCategoryPublish($id, 0);
4245
        }
4246
4247
        return api_item_property_update(
4248
            api_get_course_info(),
4249
            TOOL_LEARNPATH_CATEGORY,
4250
            $id,
4251
            $action,
4252
            api_get_user_id()
4253
        );
4254
    }
4255
4256
    /**
4257
     * Publishes a learnpath. This basically means show or hide the learnpath
4258
     * on the course homepage
4259
     * Can be used as abstract.
4260
     *
4261
     * @param int    $lp_id          Learnpath id
4262
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4263
     *
4264
     * @return bool
4265
     */
4266
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4267
    {
4268
        $course_id = api_get_course_int_id();
4269
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4270
        $lp_id = (int) $lp_id;
4271
        $sql = "SELECT * FROM $tbl_lp
4272
                WHERE iid = $lp_id";
4273
        $result = Database::query($sql);
4274
        if (Database::num_rows($result)) {
4275
            $row = Database::fetch_array($result);
4276
            $name = Database::escape_string($row['name']);
4277
            if ($set_visibility == 'i') {
4278
                $v = 0;
4279
            }
4280
            if ($set_visibility == 'v') {
4281
                $v = 1;
4282
            }
4283
4284
            $session_id = api_get_session_id();
4285
            $session_condition = api_get_session_condition($session_id);
4286
4287
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4288
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4289
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4290
4291
            $sql = "SELECT * FROM $tbl_tool
4292
                    WHERE
4293
                        c_id = $course_id AND
4294
                        (link = '$link' OR link = '$oldLink') AND
4295
                        image = 'scormbuilder.gif' AND
4296
                        (
4297
                            link LIKE '$link%' OR
4298
                            link LIKE '$oldLink%'
4299
                        )
4300
                        $session_condition
4301
                    ";
4302
4303
            $result = Database::query($sql);
4304
            $num = Database::num_rows($result);
4305
            if ($set_visibility == 'i' && $num > 0) {
4306
                $sql = "DELETE FROM $tbl_tool
4307
                        WHERE
4308
                            c_id = $course_id AND
4309
                            (link = '$link' OR link = '$oldLink') AND
4310
                            image='scormbuilder.gif'
4311
                            $session_condition";
4312
                Database::query($sql);
4313
            } elseif ($set_visibility == 'v' && $num == 0) {
4314
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4315
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4316
                Database::query($sql);
4317
                $insertId = Database::insert_id();
4318
                if ($insertId) {
4319
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4320
                    Database::query($sql);
4321
                }
4322
            } elseif ($set_visibility == 'v' && $num > 0) {
4323
                $sql = "UPDATE $tbl_tool SET
4324
                            c_id = $course_id,
4325
                            name = '$name',
4326
                            link = '$link',
4327
                            image = 'scormbuilder.gif',
4328
                            visibility = '$v',
4329
                            admin = '0',
4330
                            address = 'pastillegris.gif',
4331
                            added_tool = 0,
4332
                            session_id = $session_id
4333
                        WHERE
4334
                            c_id = ".$course_id." AND
4335
                            (link = '$link' OR link = '$oldLink') AND
4336
                            image='scormbuilder.gif'
4337
                            $session_condition
4338
                        ";
4339
                Database::query($sql);
4340
            } else {
4341
                // Parameter and database incompatible, do nothing, exit.
4342
                return false;
4343
            }
4344
        } else {
4345
            return false;
4346
        }
4347
    }
4348
4349
    /**
4350
     * Publishes a learnpath.
4351
     * Show or hide the learnpath category on the course homepage.
4352
     *
4353
     * @param int $id
4354
     * @param int $setVisibility
4355
     *
4356
     * @throws \Doctrine\ORM\NonUniqueResultException
4357
     * @throws \Doctrine\ORM\ORMException
4358
     * @throws \Doctrine\ORM\OptimisticLockException
4359
     * @throws \Doctrine\ORM\TransactionRequiredException
4360
     *
4361
     * @return bool
4362
     */
4363
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4364
    {
4365
        $courseId = api_get_course_int_id();
4366
        $sessionId = api_get_session_id();
4367
        $sessionCondition = api_get_session_condition(
4368
            $sessionId,
4369
            true,
4370
            false,
4371
            't.sessionId'
4372
        );
4373
4374
        $em = Database::getManager();
4375
4376
        /** @var CLpCategory $category */
4377
        $category = $em->find('ChamiloCourseBundle:CLpCategory', $id);
4378
4379
        if (!$category) {
4380
            return false;
4381
        }
4382
4383
        if (empty($courseId)) {
4384
            return false;
4385
        }
4386
4387
        $link = self::getCategoryLinkForTool($id);
4388
4389
        /** @var CTool $tool */
4390
        $tool = $em->createQuery("
4391
                SELECT t FROM ChamiloCourseBundle:CTool t
4392
                WHERE
4393
                    t.course = :course AND
4394
                    t.link = :link1 AND
4395
                    t.image LIKE 'lp_category.%' AND
4396
                    t.link LIKE :link2
4397
                    $sessionCondition
4398
            ")
4399
            ->setParameters([
4400
                'course' => $courseId,
4401
                'link1' => $link,
4402
                'link2' => "$link%",
4403
            ])
4404
            ->getOneOrNullResult();
4405
4406
        if ($setVisibility == 0 && $tool) {
4407
            $em->remove($tool);
4408
            $em->flush();
4409
4410
            return true;
4411
        }
4412
4413
        if ($setVisibility == 1 && !$tool) {
4414
            $tool = new CTool();
4415
            $tool
4416
                ->setCategory('authoring')
4417
                ->setCourse(api_get_course_entity($courseId))
4418
                ->setName(strip_tags($category->getName()))
4419
                ->setLink($link)
4420
                ->setImage('lp_category.png')
4421
                ->setVisibility(1)
4422
                ->setAdmin(0)
4423
                ->setAddress('pastillegris.gif')
4424
                ->setAddedTool(0)
4425
                ->setSessionId($sessionId)
4426
                ->setTarget('_self');
4427
4428
            $em->persist($tool);
4429
            $em->flush();
4430
4431
            $tool->setId($tool->getIid());
4432
4433
            $em->persist($tool);
4434
            $em->flush();
4435
4436
            return true;
4437
        }
4438
4439
        if ($setVisibility == 1 && $tool) {
4440
            $tool
4441
                ->setName(strip_tags($category->getName()))
4442
                ->setVisibility(1);
4443
4444
            $em->persist($tool);
4445
            $em->flush();
4446
4447
            return true;
4448
        }
4449
4450
        return false;
4451
    }
4452
4453
    /**
4454
     * Check if the learnpath category is visible for a user.
4455
     *
4456
     * @param CLpCategory $category
4457
     * @param User        $user
4458
     * @param int
4459
     * @param int
4460
     *
4461
     * @return bool
4462
     */
4463
    public static function categoryIsVisibleForStudent(
4464
        CLpCategory $category,
4465
        User $user,
4466
        $courseId = 0,
4467
        $sessionId = 0
4468
    ) {
4469
        $subscriptionSettings = self::getSubscriptionSettings();
4470
4471
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4472
            return true;
4473
        }
4474
4475
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4476
4477
        if ($isAllowedToEdit) {
4478
            return true;
4479
        }
4480
4481
        if (empty($category)) {
4482
            return false;
4483
        }
4484
4485
        $users = $category->getUsers();
4486
4487
        if (empty($users) || !$users->count()) {
4488
            return true;
4489
        }
4490
4491
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4492
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4493
4494
        if ($category->hasUserAdded($user)) {
4495
            return true;
4496
        }
4497
4498
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4499
        if (!empty($groups)) {
4500
            $em = Database::getManager();
4501
4502
            /** @var ItemPropertyRepository $itemRepo */
4503
            $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4504
4505
            /** @var CourseRepository $courseRepo */
4506
            $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4507
            $session = null;
4508
            if (!empty($sessionId)) {
4509
                $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4510
            }
4511
4512
            $course = $courseRepo->find($courseId);
4513
4514
            // Subscribed groups to a LP
4515
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4516
                TOOL_LEARNPATH_CATEGORY,
4517
                $category->getId(),
4518
                $course,
4519
                $session
4520
            );
4521
4522
            if (!empty($subscribedGroupsInLp)) {
4523
                $groups = array_column($groups, 'iid');
4524
                /** @var CItemProperty $item */
4525
                foreach ($subscribedGroupsInLp as $item) {
4526
                    if ($item->getGroup() &&
4527
                        in_array($item->getGroup()->getId(), $groups)
4528
                    ) {
4529
                        return true;
4530
                    }
4531
                }
4532
            }
4533
        }
4534
4535
        return false;
4536
    }
4537
4538
    /**
4539
     * Check if a learnpath category is published as course tool.
4540
     *
4541
     * @param CLpCategory $category
4542
     * @param int         $courseId
4543
     *
4544
     * @return bool
4545
     */
4546
    public static function categoryIsPublished(
4547
        CLpCategory $category,
4548
        $courseId
4549
    ) {
4550
        $link = self::getCategoryLinkForTool($category->getId());
4551
        $em = Database::getManager();
4552
4553
        $tools = $em
4554
            ->createQuery("
4555
                SELECT t FROM ChamiloCourseBundle:CTool t
4556
                WHERE t.course = :course AND 
4557
                    t.name = :name AND
4558
                    t.image LIKE 'lp_category.%' AND
4559
                    t.link LIKE :link
4560
            ")
4561
            ->setParameters([
4562
                'course' => $courseId,
4563
                'name' => strip_tags($category->getName()),
4564
                'link' => "$link%",
4565
            ])
4566
            ->getResult();
4567
4568
        /** @var CTool $tool */
4569
        $tool = current($tools);
4570
4571
        return $tool ? $tool->getVisibility() : false;
4572
    }
4573
4574
    /**
4575
     * Restart the whole learnpath. Return the URL of the first element.
4576
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4577
     * To use a similar method  statically, use the create_new_attempt() method.
4578
     *
4579
     * @return bool
4580
     */
4581
    public function restart()
4582
    {
4583
        if ($this->debug > 0) {
4584
            error_log('In learnpath::restart()', 0);
4585
        }
4586
        // TODO
4587
        // Call autosave method to save the current progress.
4588
        //$this->index = 0;
4589
        if (api_is_invitee()) {
4590
            return false;
4591
        }
4592
        $session_id = api_get_session_id();
4593
        $course_id = api_get_course_int_id();
4594
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4595
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4596
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4597
        if ($this->debug > 2) {
4598
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4599
        }
4600
        Database::query($sql);
4601
        $view_id = Database::insert_id();
4602
4603
        if ($view_id) {
4604
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4605
            Database::query($sql);
4606
            $this->lp_view_id = $view_id;
4607
            $this->attempt = $this->attempt + 1;
4608
        } else {
4609
            $this->error = 'Could not insert into item_view table...';
4610
4611
            return false;
4612
        }
4613
        $this->autocomplete_parents($this->current);
4614
        foreach ($this->items as $index => $dummy) {
4615
            $this->items[$index]->restart();
4616
            $this->items[$index]->set_lp_view($this->lp_view_id);
4617
        }
4618
        $this->first();
4619
4620
        return true;
4621
    }
4622
4623
    /**
4624
     * Saves the current item.
4625
     *
4626
     * @return bool
4627
     */
4628
    public function save_current()
4629
    {
4630
        $debug = $this->debug;
4631
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4632
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4633
        if ($debug) {
4634
            error_log('save_current() saving item '.$this->current, 0);
4635
            error_log(''.print_r($this->items, true), 0);
4636
        }
4637
        if (isset($this->items[$this->current]) &&
4638
            is_object($this->items[$this->current])
4639
        ) {
4640
            if ($debug) {
4641
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4642
            }
4643
4644
            $res = $this->items[$this->current]->save(
4645
                false,
4646
                $this->prerequisites_match($this->current)
4647
            );
4648
            $this->autocomplete_parents($this->current);
4649
            $status = $this->items[$this->current]->get_status();
4650
            $this->update_queue[$this->current] = $status;
4651
4652
            if ($debug) {
4653
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4654
            }
4655
4656
            return $res;
4657
        }
4658
4659
        return false;
4660
    }
4661
4662
    /**
4663
     * Saves the given item.
4664
     *
4665
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4666
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4667
     *
4668
     * @return bool
4669
     */
4670
    public function save_item($item_id = null, $from_outside = true)
4671
    {
4672
        $debug = $this->debug;
4673
        if ($debug) {
4674
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4675
        }
4676
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4677
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4678
        if (empty($item_id)) {
4679
            $item_id = (int) $_REQUEST['id'];
4680
        }
4681
4682
        if (empty($item_id)) {
4683
            $item_id = $this->get_current_item_id();
4684
        }
4685
        if (isset($this->items[$item_id]) &&
4686
            is_object($this->items[$item_id])
4687
        ) {
4688
            if ($debug) {
4689
                error_log('Object exists');
4690
            }
4691
4692
            // Saving the item.
4693
            $res = $this->items[$item_id]->save(
4694
                $from_outside,
4695
                $this->prerequisites_match($item_id)
4696
            );
4697
4698
            if ($debug) {
4699
                error_log('update_queue before:');
4700
                error_log(print_r($this->update_queue, 1));
4701
            }
4702
            $this->autocomplete_parents($item_id);
4703
4704
            $status = $this->items[$item_id]->get_status();
4705
            $this->update_queue[$item_id] = $status;
4706
4707
            if ($debug) {
4708
                error_log('get_status(): '.$status);
4709
                error_log('update_queue after:');
4710
                error_log(print_r($this->update_queue, 1));
4711
            }
4712
4713
            return $res;
4714
        }
4715
4716
        return false;
4717
    }
4718
4719
    /**
4720
     * Saves the last item seen's ID only in case.
4721
     */
4722
    public function save_last()
4723
    {
4724
        $course_id = api_get_course_int_id();
4725
        $debug = $this->debug;
4726
        if ($debug) {
4727
            error_log('In learnpath::save_last()', 0);
4728
        }
4729
        $session_condition = api_get_session_condition(
4730
            api_get_session_id(),
4731
            true,
4732
            false
4733
        );
4734
        $table = Database::get_course_table(TABLE_LP_VIEW);
4735
4736
        if (isset($this->current) && !api_is_invitee()) {
4737
            if ($debug) {
4738
                error_log('Saving current item ('.$this->current.') for later review', 0);
4739
            }
4740
            $sql = "UPDATE $table SET
4741
                        last_item = ".$this->get_current_item_id()."
4742
                    WHERE
4743
                        c_id = $course_id AND
4744
                        lp_id = ".$this->get_id()." AND
4745
                        user_id = ".$this->get_user_id()." ".$session_condition;
4746
4747
            if ($debug) {
4748
                error_log('Saving last item seen : '.$sql, 0);
4749
            }
4750
            Database::query($sql);
4751
        }
4752
4753
        if (!api_is_invitee()) {
4754
            // Save progress.
4755
            list($progress) = $this->get_progress_bar_text('%');
4756
            if ($progress >= 0 && $progress <= 100) {
4757
                $progress = (int) $progress;
4758
                $sql = "UPDATE $table SET
4759
                            progress = $progress
4760
                        WHERE
4761
                            c_id = $course_id AND
4762
                            lp_id = ".$this->get_id()." AND
4763
                            user_id = ".$this->get_user_id()." ".$session_condition;
4764
                // Ignore errors as some tables might not have the progress field just yet.
4765
                Database::query($sql);
4766
                $this->progress_db = $progress;
4767
            }
4768
        }
4769
    }
4770
4771
    /**
4772
     * Sets the current item ID (checks if valid and authorized first).
4773
     *
4774
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4775
     */
4776
    public function set_current_item($item_id = null)
4777
    {
4778
        $debug = $this->debug;
4779
        if ($debug) {
4780
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4781
        }
4782
        if (empty($item_id)) {
4783
            if ($debug) {
4784
                error_log('No new current item given, ignore...', 0);
4785
            }
4786
            // Do nothing.
4787
        } else {
4788
            if ($debug) {
4789
                error_log('New current item given is '.$item_id.'...', 0);
4790
            }
4791
            if (is_numeric($item_id)) {
4792
                $item_id = (int) $item_id;
4793
                // TODO: Check in database here.
4794
                $this->last = $this->current;
4795
                $this->current = $item_id;
4796
                // TODO: Update $this->index as well.
4797
                foreach ($this->ordered_items as $index => $item) {
4798
                    if ($item == $this->current) {
4799
                        $this->index = $index;
4800
                        break;
4801
                    }
4802
                }
4803
                if ($debug) {
4804
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4805
                }
4806
            } else {
4807
                if ($debug) {
4808
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4809
                }
4810
            }
4811
        }
4812
    }
4813
4814
    /**
4815
     * Sets the encoding.
4816
     *
4817
     * @param string $enc New encoding
4818
     *
4819
     * @return bool
4820
     *
4821
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
4822
     */
4823
    public function set_encoding($enc = 'UTF-8')
4824
    {
4825
        $enc = api_refine_encoding_id($enc);
4826
        if (empty($enc)) {
4827
            $enc = api_get_system_encoding();
4828
        }
4829
        if (api_is_encoding_supported($enc)) {
4830
            $lp = $this->get_id();
4831
            if ($lp != 0) {
4832
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4833
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
4834
                        WHERE iid = ".$lp;
4835
                $res = Database::query($sql);
4836
4837
                return $res;
4838
            }
4839
        }
4840
4841
        return false;
4842
    }
4843
4844
    /**
4845
     * Sets the JS lib setting in the database directly.
4846
     * This is the JavaScript library file this lp needs to load on startup.
4847
     *
4848
     * @param string $lib Proximity setting
4849
     *
4850
     * @return bool True on update success. False otherwise.
4851
     */
4852
    public function set_jslib($lib = '')
4853
    {
4854
        $lp = $this->get_id();
4855
4856
        if ($lp != 0) {
4857
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4858
            $lib = Database::escape_string($lib);
4859
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
4860
                    WHERE iid = $lp";
4861
            $res = Database::query($sql);
4862
4863
            return $res;
4864
        }
4865
4866
        return false;
4867
    }
4868
4869
    /**
4870
     * Sets the name of the LP maker (publisher) (and save).
4871
     *
4872
     * @param string $name Optional string giving the new content_maker of this learnpath
4873
     *
4874
     * @return bool True
4875
     */
4876
    public function set_maker($name = '')
4877
    {
4878
        if (empty($name)) {
4879
            return false;
4880
        }
4881
        $this->maker = $name;
4882
        $table = Database::get_course_table(TABLE_LP_MAIN);
4883
        $lp_id = $this->get_id();
4884
        $sql = "UPDATE $table SET
4885
                content_maker = '".Database::escape_string($this->maker)."'
4886
                WHERE iid = $lp_id";
4887
        Database::query($sql);
4888
4889
        return true;
4890
    }
4891
4892
    /**
4893
     * Sets the name of the current learnpath (and save).
4894
     *
4895
     * @param string $name Optional string giving the new name of this learnpath
4896
     *
4897
     * @return bool True/False
4898
     */
4899
    public function set_name($name = null)
4900
    {
4901
        if (empty($name)) {
4902
            return false;
4903
        }
4904
        $this->name = Database::escape_string($name);
4905
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4906
        $lp_id = $this->get_id();
4907
        $course_id = $this->course_info['real_id'];
4908
        $sql = "UPDATE $lp_table SET
4909
                name = '".Database::escape_string($this->name)."'
4910
                WHERE iid = $lp_id";
4911
        $result = Database::query($sql);
4912
        // If the lp is visible on the homepage, change his name there.
4913
        if (Database::affected_rows($result)) {
4914
            $session_id = api_get_session_id();
4915
            $session_condition = api_get_session_condition($session_id);
4916
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4917
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4918
            $sql = "UPDATE $tbl_tool SET name = '$this->name'
4919
            	    WHERE
4920
            	        c_id = $course_id AND
4921
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
4922
            Database::query($sql);
4923
4924
            return true;
4925
        }
4926
4927
        return false;
4928
    }
4929
4930
    /**
4931
     * Set index specified prefix terms for all items in this path.
4932
     *
4933
     * @param string $terms_string Comma-separated list of terms
4934
     * @param string $prefix       Xapian term prefix
4935
     *
4936
     * @return bool False on error, true otherwise
4937
     */
4938
    public function set_terms_by_prefix($terms_string, $prefix)
4939
    {
4940
        $course_id = api_get_course_int_id();
4941
        if (api_get_setting('search_enabled') !== 'true') {
4942
            return false;
4943
        }
4944
4945
        if (!extension_loaded('xapian')) {
4946
            return false;
4947
        }
4948
4949
        $terms_string = trim($terms_string);
4950
        $terms = explode(',', $terms_string);
4951
        array_walk($terms, 'trim_value');
4952
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
4953
4954
        // Don't do anything if no change, verify only at DB, not the search engine.
4955
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
4956
            return false;
4957
        }
4958
4959
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
4960
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
4961
4962
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
4963
        // TODO: Make query secure agains XSS : use member attr instead of post var.
4964
        $lp_id = (int) $_POST['lp_id'];
4965
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
4966
        $result = Database::query($sql);
4967
        $di = new ChamiloIndexer();
4968
4969
        while ($lp_item = Database::fetch_array($result)) {
4970
            // Get search_did.
4971
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
4972
            $sql = 'SELECT * FROM %s
4973
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
4974
                    LIMIT 1';
4975
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
4976
4977
            //echo $sql; echo '<br>';
4978
            $res = Database::query($sql);
4979
            if (Database::num_rows($res) > 0) {
4980
                $se_ref = Database::fetch_array($res);
4981
                // Compare terms.
4982
                $doc = $di->get_document($se_ref['search_did']);
4983
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
4984
                $xterms = [];
4985
                foreach ($xapian_terms as $xapian_term) {
4986
                    $xterms[] = substr($xapian_term['name'], 1);
4987
                }
4988
4989
                $dterms = $terms;
4990
                $missing_terms = array_diff($dterms, $xterms);
4991
                $deprecated_terms = array_diff($xterms, $dterms);
4992
4993
                // Save it to search engine.
4994
                foreach ($missing_terms as $term) {
4995
                    $doc->add_term($prefix.$term, 1);
4996
                }
4997
                foreach ($deprecated_terms as $term) {
4998
                    $doc->remove_term($prefix.$term);
4999
                }
5000
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5001
                $di->getDb()->flush();
5002
            }
5003
        }
5004
5005
        return true;
5006
    }
5007
5008
    /**
5009
     * Sets the theme of the LP (local/remote) (and save).
5010
     *
5011
     * @param string $name Optional string giving the new theme of this learnpath
5012
     *
5013
     * @return bool Returns true if theme name is not empty
5014
     */
5015
    public function set_theme($name = '')
5016
    {
5017
        $this->theme = $name;
5018
        $table = Database::get_course_table(TABLE_LP_MAIN);
5019
        $lp_id = $this->get_id();
5020
        $sql = "UPDATE $table
5021
                SET theme = '".Database::escape_string($this->theme)."'
5022
                WHERE iid = $lp_id";
5023
        Database::query($sql);
5024
5025
        return true;
5026
    }
5027
5028
    /**
5029
     * Sets the image of an LP (and save).
5030
     *
5031
     * @param string $name Optional string giving the new image of this learnpath
5032
     *
5033
     * @return bool Returns true if theme name is not empty
5034
     */
5035
    public function set_preview_image($name = '')
5036
    {
5037
        $this->preview_image = $name;
5038
        $table = Database::get_course_table(TABLE_LP_MAIN);
5039
        $lp_id = $this->get_id();
5040
        $sql = "UPDATE $table SET
5041
                preview_image = '".Database::escape_string($this->preview_image)."'
5042
                WHERE iid = $lp_id";
5043
        Database::query($sql);
5044
5045
        return true;
5046
    }
5047
5048
    /**
5049
     * Sets the author of a LP (and save).
5050
     *
5051
     * @param string $name Optional string giving the new author of this learnpath
5052
     *
5053
     * @return bool Returns true if author's name is not empty
5054
     */
5055
    public function set_author($name = '')
5056
    {
5057
        $this->author = $name;
5058
        $table = Database::get_course_table(TABLE_LP_MAIN);
5059
        $lp_id = $this->get_id();
5060
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5061
                WHERE iid = $lp_id";
5062
        Database::query($sql);
5063
5064
        return true;
5065
    }
5066
5067
    /**
5068
     * Sets the hide_toc_frame parameter of a LP (and save).
5069
     *
5070
     * @param int $hide 1 if frame is hidden 0 then else
5071
     *
5072
     * @return bool Returns true if author's name is not empty
5073
     */
5074
    public function set_hide_toc_frame($hide)
5075
    {
5076
        if (intval($hide) == $hide) {
5077
            $this->hide_toc_frame = $hide;
5078
            $table = Database::get_course_table(TABLE_LP_MAIN);
5079
            $lp_id = $this->get_id();
5080
            $sql = "UPDATE $table SET
5081
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5082
                    WHERE iid = $lp_id";
5083
            Database::query($sql);
5084
5085
            return true;
5086
        }
5087
5088
        return false;
5089
    }
5090
5091
    /**
5092
     * Sets the prerequisite of a LP (and save).
5093
     *
5094
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5095
     *
5096
     * @return bool returns true if prerequisite is not empty
5097
     */
5098
    public function set_prerequisite($prerequisite)
5099
    {
5100
        $this->prerequisite = (int) $prerequisite;
5101
        $table = Database::get_course_table(TABLE_LP_MAIN);
5102
        $lp_id = $this->get_id();
5103
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5104
                WHERE iid = $lp_id";
5105
        Database::query($sql);
5106
5107
        return true;
5108
    }
5109
5110
    /**
5111
     * Sets the location/proximity of the LP (local/remote) (and save).
5112
     *
5113
     * @param string $name Optional string giving the new location of this learnpath
5114
     *
5115
     * @return bool True on success / False on error
5116
     */
5117
    public function set_proximity($name = '')
5118
    {
5119
        if (empty($name)) {
5120
            return false;
5121
        }
5122
5123
        $this->proximity = $name;
5124
        $table = Database::get_course_table(TABLE_LP_MAIN);
5125
        $lp_id = $this->get_id();
5126
        $sql = "UPDATE $table SET
5127
                    content_local = '".Database::escape_string($name)."'
5128
                WHERE iid = $lp_id";
5129
        Database::query($sql);
5130
5131
        return true;
5132
    }
5133
5134
    /**
5135
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5136
     *
5137
     * @param int $id DB ID of the item
5138
     */
5139
    public function set_previous_item($id)
5140
    {
5141
        if ($this->debug > 0) {
5142
            error_log('In learnpath::set_previous_item()', 0);
5143
        }
5144
        $this->last = $id;
5145
    }
5146
5147
    /**
5148
     * Sets use_max_score.
5149
     *
5150
     * @param int $use_max_score Optional string giving the new location of this learnpath
5151
     *
5152
     * @return bool True on success / False on error
5153
     */
5154
    public function set_use_max_score($use_max_score = 1)
5155
    {
5156
        $use_max_score = (int) $use_max_score;
5157
        $this->use_max_score = $use_max_score;
5158
        $table = Database::get_course_table(TABLE_LP_MAIN);
5159
        $lp_id = $this->get_id();
5160
        $sql = "UPDATE $table SET
5161
                    use_max_score = '".$this->use_max_score."'
5162
                WHERE iid = $lp_id";
5163
        Database::query($sql);
5164
5165
        return true;
5166
    }
5167
5168
    /**
5169
     * Sets and saves the expired_on date.
5170
     *
5171
     * @param string $expired_on Optional string giving the new author of this learnpath
5172
     *
5173
     * @throws \Doctrine\ORM\OptimisticLockException
5174
     *
5175
     * @return bool Returns true if author's name is not empty
5176
     */
5177
    public function set_expired_on($expired_on)
5178
    {
5179
        $em = Database::getManager();
5180
        /** @var CLp $lp */
5181
        $lp = $em
5182
            ->getRepository('ChamiloCourseBundle:CLp')
5183
            ->findOneBy(
5184
                [
5185
                    'iid' => $this->get_id(),
5186
                ]
5187
            );
5188
5189
        if (!$lp) {
5190
            return false;
5191
        }
5192
5193
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5194
5195
        $lp->setExpiredOn($this->expired_on);
5196
        $em->persist($lp);
5197
        $em->flush();
5198
5199
        return true;
5200
    }
5201
5202
    /**
5203
     * Sets and saves the publicated_on date.
5204
     *
5205
     * @param string $publicated_on Optional string giving the new author of this learnpath
5206
     *
5207
     * @throws \Doctrine\ORM\OptimisticLockException
5208
     *
5209
     * @return bool Returns true if author's name is not empty
5210
     */
5211
    public function set_publicated_on($publicated_on)
5212
    {
5213
        $em = Database::getManager();
5214
        /** @var CLp $lp */
5215
        $lp = $em
5216
            ->getRepository('ChamiloCourseBundle:CLp')
5217
            ->findOneBy(
5218
                [
5219
                    'iid' => $this->get_id(),
5220
                ]
5221
            );
5222
5223
        if (!$lp) {
5224
            return false;
5225
        }
5226
5227
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5228
        $lp->setPublicatedOn($this->publicated_on);
5229
        $em->persist($lp);
5230
        $em->flush();
5231
5232
        return true;
5233
    }
5234
5235
    /**
5236
     * Sets and saves the expired_on date.
5237
     *
5238
     * @return bool Returns true if author's name is not empty
5239
     */
5240
    public function set_modified_on()
5241
    {
5242
        $this->modified_on = api_get_utc_datetime();
5243
        $table = Database::get_course_table(TABLE_LP_MAIN);
5244
        $lp_id = $this->get_id();
5245
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5246
                WHERE iid = $lp_id";
5247
        Database::query($sql);
5248
5249
        return true;
5250
    }
5251
5252
    /**
5253
     * Sets the object's error message.
5254
     *
5255
     * @param string $error Error message. If empty, reinits the error string
5256
     */
5257
    public function set_error_msg($error = '')
5258
    {
5259
        if ($this->debug > 0) {
5260
            error_log('In learnpath::set_error_msg()', 0);
5261
        }
5262
        if (empty($error)) {
5263
            $this->error = '';
5264
        } else {
5265
            $this->error .= $error;
5266
        }
5267
    }
5268
5269
    /**
5270
     * Launches the current item if not 'sco'
5271
     * (starts timer and make sure there is a record ready in the DB).
5272
     *
5273
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5274
     *
5275
     * @return bool
5276
     */
5277
    public function start_current_item($allow_new_attempt = false)
5278
    {
5279
        $debug = $this->debug;
5280
        if ($debug) {
5281
            error_log('In learnpath::start_current_item()');
5282
            error_log('current: '.$this->current);
5283
        }
5284
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5285
            $type = $this->get_type();
5286
            $item_type = $this->items[$this->current]->get_type();
5287
            if (($type == 2 && $item_type != 'sco') ||
5288
                ($type == 3 && $item_type != 'au') ||
5289
                ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)
5290
            ) {
5291
                if ($debug) {
5292
                    error_log('item type: '.$item_type);
5293
                    error_log('lp type: '.$type);
5294
                }
5295
                $this->items[$this->current]->open($allow_new_attempt);
5296
                $this->autocomplete_parents($this->current);
5297
                $prereq_check = $this->prerequisites_match($this->current);
5298
                if ($debug) {
5299
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5300
                }
5301
                $this->items[$this->current]->save(false, $prereq_check);
5302
            }
5303
            // If sco, then it is supposed to have been updated by some other call.
5304
            if ($item_type == 'sco') {
5305
                $this->items[$this->current]->restart();
5306
            }
5307
        }
5308
        if ($debug) {
5309
            error_log('lp_view_session_id');
5310
            error_log($this->lp_view_session_id);
5311
            error_log('api session id');
5312
            error_log(api_get_session_id());
5313
            error_log('End of learnpath::start_current_item()');
5314
        }
5315
5316
        return true;
5317
    }
5318
5319
    /**
5320
     * Stops the processing and counters for the old item (as held in $this->last).
5321
     *
5322
     * @return bool True/False
5323
     */
5324
    public function stop_previous_item()
5325
    {
5326
        $debug = $this->debug;
5327
        if ($debug) {
5328
            error_log('In learnpath::stop_previous_item()', 0);
5329
        }
5330
5331
        if ($this->last != 0 && $this->last != $this->current &&
5332
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5333
        ) {
5334
            if ($debug) {
5335
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5336
            }
5337
            switch ($this->get_type()) {
5338
                case '3':
5339
                    if ($this->items[$this->last]->get_type() != 'au') {
5340
                        if ($debug) {
5341
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5342
                        }
5343
                        $this->items[$this->last]->close();
5344
                    } else {
5345
                        if ($debug) {
5346
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5347
                        }
5348
                    }
5349
                    break;
5350
                case '2':
5351
                    if ($this->items[$this->last]->get_type() != 'sco') {
5352
                        if ($debug) {
5353
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5354
                        }
5355
                        $this->items[$this->last]->close();
5356
                    } else {
5357
                        if ($debug) {
5358
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5359
                        }
5360
                    }
5361
                    break;
5362
                case '1':
5363
                default:
5364
                    if ($debug) {
5365
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5366
                    }
5367
                    $this->items[$this->last]->close();
5368
                    break;
5369
            }
5370
        } else {
5371
            if ($debug) {
5372
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5373
            }
5374
5375
            return false;
5376
        }
5377
5378
        return true;
5379
    }
5380
5381
    /**
5382
     * Updates the default view mode from fullscreen to embedded and inversely.
5383
     *
5384
     * @return string The current default view mode ('fullscreen' or 'embedded')
5385
     */
5386
    public function update_default_view_mode()
5387
    {
5388
        $table = Database::get_course_table(TABLE_LP_MAIN);
5389
        $sql = "SELECT * FROM $table
5390
                WHERE iid = ".$this->get_id();
5391
        $res = Database::query($sql);
5392
        if (Database::num_rows($res) > 0) {
5393
            $row = Database::fetch_array($res);
5394
            $default_view_mode = $row['default_view_mod'];
5395
            $view_mode = $default_view_mode;
5396
            switch ($default_view_mode) {
5397
                case 'fullscreen': // default with popup
5398
                    $view_mode = 'embedded';
5399
                    break;
5400
                case 'embedded': // default view with left menu
5401
                    $view_mode = 'embedframe';
5402
                    break;
5403
                case 'embedframe': //folded menu
5404
                    $view_mode = 'impress';
5405
                    break;
5406
                case 'impress':
5407
                    $view_mode = 'fullscreen';
5408
                    break;
5409
            }
5410
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5411
                    WHERE iid = ".$this->get_id();
5412
            Database::query($sql);
5413
            $this->mode = $view_mode;
5414
5415
            return $view_mode;
5416
        }
5417
5418
        return -1;
5419
    }
5420
5421
    /**
5422
     * Updates the default behaviour about auto-commiting SCORM updates.
5423
     *
5424
     * @return bool True if auto-commit has been set to 'on', false otherwise
5425
     */
5426
    public function update_default_scorm_commit()
5427
    {
5428
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5429
        $sql = "SELECT * FROM $lp_table
5430
                WHERE iid = ".$this->get_id();
5431
        $res = Database::query($sql);
5432
        if (Database::num_rows($res) > 0) {
5433
            $row = Database::fetch_array($res);
5434
            $force = $row['force_commit'];
5435
            if ($force == 1) {
5436
                $force = 0;
5437
                $force_return = false;
5438
            } elseif ($force == 0) {
5439
                $force = 1;
5440
                $force_return = true;
5441
            }
5442
            $sql = "UPDATE $lp_table SET force_commit = $force
5443
                    WHERE iid = ".$this->get_id();
5444
            Database::query($sql);
5445
            $this->force_commit = $force_return;
5446
5447
            return $force_return;
5448
        }
5449
5450
        return -1;
5451
    }
5452
5453
    /**
5454
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5455
     *
5456
     * @return bool True on success, false on failure
5457
     */
5458
    public function update_display_order()
5459
    {
5460
        $course_id = api_get_course_int_id();
5461
        $table = Database::get_course_table(TABLE_LP_MAIN);
5462
        $sql = "SELECT * FROM $table
5463
                WHERE c_id = $course_id
5464
                ORDER BY display_order";
5465
        $res = Database::query($sql);
5466
        if ($res === false) {
5467
            return false;
5468
        }
5469
5470
        $num = Database::num_rows($res);
5471
        // First check the order is correct, globally (might be wrong because
5472
        // of versions < 1.8.4).
5473
        if ($num > 0) {
5474
            $i = 1;
5475
            while ($row = Database::fetch_array($res)) {
5476
                if ($row['display_order'] != $i) {
5477
                    // If we find a gap in the order, we need to fix it.
5478
                    $sql = "UPDATE $table SET display_order = $i
5479
                            WHERE iid = ".$row['iid'];
5480
                    Database::query($sql);
5481
                }
5482
                $i++;
5483
            }
5484
        }
5485
5486
        return true;
5487
    }
5488
5489
    /**
5490
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5491
     *
5492
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5493
     */
5494
    public function update_reinit()
5495
    {
5496
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5497
        $sql = "SELECT * FROM $lp_table
5498
                WHERE iid = ".$this->get_id();
5499
        $res = Database::query($sql);
5500
        if (Database::num_rows($res) > 0) {
5501
            $row = Database::fetch_array($res);
5502
            $force = $row['prevent_reinit'];
5503
            if ($force == 1) {
5504
                $force = 0;
5505
            } elseif ($force == 0) {
5506
                $force = 1;
5507
            }
5508
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5509
                    WHERE iid = ".$this->get_id();
5510
            Database::query($sql);
5511
            $this->prevent_reinit = $force;
5512
5513
            return $force;
5514
        }
5515
5516
        return -1;
5517
    }
5518
5519
    /**
5520
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5521
     *
5522
     * @return string 'single', 'multi' or 'seriousgame'
5523
     *
5524
     * @author ndiechburg <[email protected]>
5525
     */
5526
    public function get_attempt_mode()
5527
    {
5528
        //Set default value for seriousgame_mode
5529
        if (!isset($this->seriousgame_mode)) {
5530
            $this->seriousgame_mode = 0;
5531
        }
5532
        // Set default value for prevent_reinit
5533
        if (!isset($this->prevent_reinit)) {
5534
            $this->prevent_reinit = 1;
5535
        }
5536
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5537
            return 'seriousgame';
5538
        }
5539
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5540
            return 'single';
5541
        }
5542
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5543
            return 'multiple';
5544
        }
5545
5546
        return 'single';
5547
    }
5548
5549
    /**
5550
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5551
     *
5552
     * @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...
5553
     *
5554
     * @return bool
5555
     *
5556
     * @author ndiechburg <[email protected]>
5557
     */
5558
    public function set_attempt_mode($mode)
5559
    {
5560
        switch ($mode) {
5561
            case 'seriousgame':
5562
                $sg_mode = 1;
5563
                $prevent_reinit = 1;
5564
                break;
5565
            case 'single':
5566
                $sg_mode = 0;
5567
                $prevent_reinit = 1;
5568
                break;
5569
            case 'multiple':
5570
                $sg_mode = 0;
5571
                $prevent_reinit = 0;
5572
                break;
5573
            default:
5574
                $sg_mode = 0;
5575
                $prevent_reinit = 0;
5576
                break;
5577
        }
5578
        $this->prevent_reinit = $prevent_reinit;
5579
        $this->seriousgame_mode = $sg_mode;
5580
        $table = Database::get_course_table(TABLE_LP_MAIN);
5581
        $sql = "UPDATE $table SET
5582
                prevent_reinit = $prevent_reinit ,
5583
                seriousgame_mode = $sg_mode
5584
                WHERE iid = ".$this->get_id();
5585
        $res = Database::query($sql);
5586
        if ($res) {
5587
            return true;
5588
        } else {
5589
            return false;
5590
        }
5591
    }
5592
5593
    /**
5594
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5595
     *
5596
     * @author ndiechburg <[email protected]>
5597
     */
5598
    public function switch_attempt_mode()
5599
    {
5600
        $mode = $this->get_attempt_mode();
5601
        switch ($mode) {
5602
            case 'single':
5603
                $next_mode = 'multiple';
5604
                break;
5605
            case 'multiple':
5606
                $next_mode = 'seriousgame';
5607
                break;
5608
            case 'seriousgame':
5609
            default:
5610
                $next_mode = 'single';
5611
                break;
5612
        }
5613
        $this->set_attempt_mode($next_mode);
5614
    }
5615
5616
    /**
5617
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5618
     * but possibility to do again a completed item.
5619
     *
5620
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5621
     *
5622
     * @author ndiechburg <[email protected]>
5623
     */
5624
    public function set_seriousgame_mode()
5625
    {
5626
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5627
        $sql = "SELECT * FROM $lp_table
5628
                WHERE iid = ".$this->get_id();
5629
        $res = Database::query($sql);
5630
        if (Database::num_rows($res) > 0) {
5631
            $row = Database::fetch_array($res);
5632
            $force = $row['seriousgame_mode'];
5633
            if ($force == 1) {
5634
                $force = 0;
5635
            } elseif ($force == 0) {
5636
                $force = 1;
5637
            }
5638
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5639
			        WHERE iid = ".$this->get_id();
5640
            Database::query($sql);
5641
            $this->seriousgame_mode = $force;
5642
5643
            return $force;
5644
        }
5645
5646
        return -1;
5647
    }
5648
5649
    /**
5650
     * Updates the "scorm_debug" value that shows or hide the debug window.
5651
     *
5652
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5653
     */
5654
    public function update_scorm_debug()
5655
    {
5656
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5657
        $sql = "SELECT * FROM $lp_table
5658
                WHERE iid = ".$this->get_id();
5659
        $res = Database::query($sql);
5660
        if (Database::num_rows($res) > 0) {
5661
            $row = Database::fetch_array($res);
5662
            $force = $row['debug'];
5663
            if ($force == 1) {
5664
                $force = 0;
5665
            } elseif ($force == 0) {
5666
                $force = 1;
5667
            }
5668
            $sql = "UPDATE $lp_table SET debug = $force
5669
                    WHERE iid = ".$this->get_id();
5670
            Database::query($sql);
5671
            $this->scorm_debug = $force;
5672
5673
            return $force;
5674
        }
5675
5676
        return -1;
5677
    }
5678
5679
    /**
5680
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5681
     *
5682
     * @author Kevin Van Den Haute
5683
     *
5684
     * @param  array
5685
     */
5686
    public function tree_array($array)
5687
    {
5688
        $array = $this->sort_tree_array($array);
5689
        $this->create_tree_array($array);
5690
    }
5691
5692
    /**
5693
     * Creates an array with the elements of the learning path tree in it.
5694
     *
5695
     * @author Kevin Van Den Haute
5696
     *
5697
     * @param array $array
5698
     * @param int   $parent
5699
     * @param int   $depth
5700
     * @param array $tmp
5701
     */
5702
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5703
    {
5704
        if (is_array($array)) {
5705
            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...
5706
                if ($array[$i]['parent_item_id'] == $parent) {
5707
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5708
                        $tmp[] = $array[$i]['parent_item_id'];
5709
                        $depth++;
5710
                    }
5711
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5712
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5713
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5714
5715
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5716
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5717
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5718
                    $this->arrMenu[] = [
5719
                        'id' => $array[$i]['id'],
5720
                        'ref' => $ref,
5721
                        'item_type' => $array[$i]['item_type'],
5722
                        'title' => $array[$i]['title'],
5723
                        'title_raw' => $array[$i]['title_raw'],
5724
                        'path' => $path,
5725
                        'description' => $array[$i]['description'],
5726
                        'parent_item_id' => $array[$i]['parent_item_id'],
5727
                        'previous_item_id' => $array[$i]['previous_item_id'],
5728
                        'next_item_id' => $array[$i]['next_item_id'],
5729
                        'min_score' => $array[$i]['min_score'],
5730
                        'max_score' => $array[$i]['max_score'],
5731
                        'mastery_score' => $array[$i]['mastery_score'],
5732
                        'display_order' => $array[$i]['display_order'],
5733
                        'prerequisite' => $preq,
5734
                        'depth' => $depth,
5735
                        'audio' => $audio,
5736
                        'prerequisite_min_score' => $prerequisiteMinScore,
5737
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5738
                    ];
5739
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5740
                }
5741
            }
5742
        }
5743
    }
5744
5745
    /**
5746
     * Sorts a multi dimensional array by parent id and display order.
5747
     *
5748
     * @author Kevin Van Den Haute
5749
     *
5750
     * @param array $array (array with al the learning path items in it)
5751
     *
5752
     * @return array
5753
     */
5754
    public function sort_tree_array($array)
5755
    {
5756
        foreach ($array as $key => $row) {
5757
            $parent[$key] = $row['parent_item_id'];
5758
            $position[$key] = $row['display_order'];
5759
        }
5760
5761
        if (count($array) > 0) {
5762
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5763
        }
5764
5765
        return $array;
5766
    }
5767
5768
    /**
5769
     * Function that creates a html list of learning path items so that we can add audio files to them.
5770
     *
5771
     * @author Kevin Van Den Haute
5772
     *
5773
     * @return string
5774
     */
5775
    public function overview()
5776
    {
5777
        $return = '';
5778
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5779
5780
        // we need to start a form when we want to update all the mp3 files
5781
        if ($update_audio == 'true') {
5782
            $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">';
5783
        }
5784
        $return .= '<div id="message"></div>';
5785
        if (count($this->items) == 0) {
5786
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5787
        } else {
5788
            $return_audio = '<table class="data_table">';
5789
            $return_audio .= '<tr>';
5790
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5791
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5792
            $return_audio .= '</tr>';
5793
5794
            if ($update_audio != 'true') {
5795
                $return .= '<div class="col-md-12">';
5796
                $return .= self::return_new_tree($update_audio);
5797
                $return .= '</div>';
5798
                $return .= Display::div(
5799
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5800
                    ['style' => 'float:left; margin-top:15px;width:100%']
5801
                );
5802
            } else {
5803
                $return_audio .= self::return_new_tree($update_audio);
5804
                $return .= $return_audio.'</table>';
5805
            }
5806
5807
            // We need to close the form when we are updating the mp3 files.
5808
            if ($update_audio == 'true') {
5809
                $return .= '<div class="footer-audio">';
5810
                $return .= Display::button(
5811
                    'save_audio',
5812
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5813
                    ['class' => 'btn btn-primary', 'type' => 'submit']
5814
                );
5815
                $return .= '</div>';
5816
            }
5817
        }
5818
5819
        // We need to close the form when we are updating the mp3 files.
5820
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
5821
            $return .= '</form>';
5822
        }
5823
5824
        return $return;
5825
    }
5826
5827
    /**
5828
     * @param string $update_audio
5829
     *
5830
     * @return array
5831
     */
5832
    public function processBuildMenuElements($update_audio = 'false')
5833
    {
5834
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
5835
        $arrLP = $this->getItemsForForm();
5836
5837
        $this->tree_array($arrLP);
5838
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
5839
        unset($this->arrMenu);
5840
        $default_data = null;
5841
        $default_content = null;
5842
        $elements = [];
5843
        $return_audio = null;
5844
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
5845
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
5846
        $countItems = count($arrLP);
5847
5848
        $upIcon = Display::return_icon(
5849
            'up.png',
5850
            get_lang('Up'),
5851
            [],
5852
            ICON_SIZE_TINY
5853
        );
5854
5855
        $disableUpIcon = Display::return_icon(
5856
            'up_na.png',
5857
            get_lang('Up'),
5858
            [],
5859
            ICON_SIZE_TINY
5860
        );
5861
5862
        $downIcon = Display::return_icon(
5863
            'down.png',
5864
            get_lang('Down'),
5865
            [],
5866
            ICON_SIZE_TINY
5867
        );
5868
5869
        $disableDownIcon = Display::return_icon(
5870
            'down_na.png',
5871
            get_lang('Down'),
5872
            [],
5873
            ICON_SIZE_TINY
5874
        );
5875
5876
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
5877
5878
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
5879
        $plugin = null;
5880
        if ($pluginCalendar) {
5881
            $plugin = LearningCalendarPlugin::create();
5882
        }
5883
5884
        for ($i = 0; $i < $countItems; $i++) {
5885
            $parent_id = $arrLP[$i]['parent_item_id'];
5886
            $title = $arrLP[$i]['title'];
5887
            $title_cut = $arrLP[$i]['title_raw'];
5888
            if ($show === false) {
5889
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
5890
            }
5891
            // Link for the documents
5892
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
5893
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
5894
                $title_cut = Display::url(
5895
                    $title_cut,
5896
                    $url,
5897
                    [
5898
                        'class' => 'ajax moved',
5899
                        'data-title' => $title,
5900
                        'title' => $title,
5901
                    ]
5902
                );
5903
            }
5904
5905
            // Detect if type is FINAL_ITEM to set path_id to SESSION
5906
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
5907
                Session::write('pathItem', $arrLP[$i]['path']);
5908
            }
5909
5910
            $oddClass = 'row_even';
5911
            if (($i % 2) == 0) {
5912
                $oddClass = 'row_odd';
5913
            }
5914
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
5915
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
5916
5917
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
5918
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
5919
            } else {
5920
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
5921
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
5922
                } else {
5923
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
5924
                        $icon = Display::return_icon('certificate.png');
5925
                    } else {
5926
                        $icon = Display::return_icon('folder_document.png');
5927
                    }
5928
                }
5929
            }
5930
5931
            // The audio column.
5932
            $return_audio .= '<td align="left" style="padding-left:10px;">';
5933
            $audio = '';
5934
            if (!$update_audio || $update_audio != 'true') {
5935
                if (empty($arrLP[$i]['audio'])) {
5936
                    $audio .= '';
5937
                }
5938
            } else {
5939
                $types = self::getChapterTypes();
5940
                if (!in_array($arrLP[$i]['item_type'], $types)) {
5941
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
5942
                    if (!empty($arrLP[$i]['audio'])) {
5943
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
5944
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
5945
                    }
5946
                }
5947
            }
5948
5949
            $return_audio .= Display::span($icon.' '.$title).
5950
                Display::tag(
5951
                    'td',
5952
                    $audio,
5953
                    ['style' => '']
5954
                );
5955
            $return_audio .= '</td>';
5956
            $move_icon = '';
5957
            $move_item_icon = '';
5958
            $edit_icon = '';
5959
            $delete_icon = '';
5960
            $audio_icon = '';
5961
            $prerequisities_icon = '';
5962
            $forumIcon = '';
5963
            $previewIcon = '';
5964
            $pluginCalendarIcon = '';
5965
            $orderIcons = '';
5966
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
5967
5968
            if ($is_allowed_to_edit) {
5969
                if (!$update_audio || $update_audio != 'true') {
5970
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
5971
                        $move_icon .= '<a class="moved" href="#">';
5972
                        $move_icon .= Display::return_icon(
5973
                            'move_everywhere.png',
5974
                            get_lang('Move'),
5975
                            [],
5976
                            ICON_SIZE_TINY
5977
                        );
5978
                        $move_icon .= '</a>';
5979
                    }
5980
                }
5981
5982
                // No edit for this item types
5983
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
5984
                    if ($arrLP[$i]['item_type'] != 'dir') {
5985
                        $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">';
5986
                        $edit_icon .= Display::return_icon(
5987
                            'edit.png',
5988
                            get_lang('LearnpathEditModule'),
5989
                            [],
5990
                            ICON_SIZE_TINY
5991
                        );
5992
                        $edit_icon .= '</a>';
5993
5994
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
5995
                            $forumThread = null;
5996
                            if (isset($this->items[$arrLP[$i]['id']])) {
5997
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
5998
                                    $this->course_int_id,
5999
                                    $this->lp_session_id
6000
                                );
6001
                            }
6002
                            if ($forumThread) {
6003
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6004
                                        'action' => 'dissociate_forum',
6005
                                        'id' => $arrLP[$i]['id'],
6006
                                        'lp_id' => $this->lp_id,
6007
                                    ]);
6008
                                $forumIcon = Display::url(
6009
                                    Display::return_icon(
6010
                                        'forum.png',
6011
                                        get_lang('DissociateForumToLPItem'),
6012
                                        [],
6013
                                        ICON_SIZE_TINY
6014
                                    ),
6015
                                    $forumIconUrl,
6016
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6017
                                );
6018
                            } else {
6019
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6020
                                        'action' => 'create_forum',
6021
                                        'id' => $arrLP[$i]['id'],
6022
                                        'lp_id' => $this->lp_id,
6023
                                    ]);
6024
                                $forumIcon = Display::url(
6025
                                    Display::return_icon(
6026
                                        'forum.png',
6027
                                        get_lang('AssociateForumToLPItem'),
6028
                                        [],
6029
                                        ICON_SIZE_TINY
6030
                                    ),
6031
                                    $forumIconUrl,
6032
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6033
                                );
6034
                            }
6035
                        }
6036
                    } else {
6037
                        $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">';
6038
                        $edit_icon .= Display::return_icon(
6039
                            'edit.png',
6040
                            get_lang('LearnpathEditModule'),
6041
                            [],
6042
                            ICON_SIZE_TINY
6043
                        );
6044
                        $edit_icon .= '</a>';
6045
                    }
6046
                } else {
6047
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6048
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6049
                        $edit_icon .= Display::return_icon(
6050
                            'edit.png',
6051
                            get_lang('Edit'),
6052
                            [],
6053
                            ICON_SIZE_TINY
6054
                        );
6055
                        $edit_icon .= '</a>';
6056
                    }
6057
                }
6058
6059
                if ($pluginCalendar) {
6060
                    $pluginLink = $pluginUrl.
6061
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6062
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6063
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6064
                    if ($itemInfo && $itemInfo['value'] == 1) {
6065
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6066
                    }
6067
                    $pluginCalendarIcon = Display::url(
6068
                        $iconCalendar,
6069
                        $pluginLink,
6070
                        ['class' => 'btn btn-default']
6071
                    );
6072
                }
6073
6074
                if ($arrLP[$i]['item_type'] != 'final_item') {
6075
                    $orderIcons = Display::url(
6076
                        $upIcon,
6077
                        'javascript:void(0)',
6078
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6079
                    );
6080
                    $orderIcons .= Display::url(
6081
                        $downIcon,
6082
                        'javascript:void(0)',
6083
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6084
                    );
6085
                }
6086
6087
                $delete_icon .= ' <a 
6088
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" 
6089
                    onclick="return confirmation(\''.addslashes($title).'\');" 
6090
                    class="btn btn-default">';
6091
                $delete_icon .= Display::return_icon(
6092
                    'delete.png',
6093
                    get_lang('LearnpathDeleteModule'),
6094
                    [],
6095
                    ICON_SIZE_TINY
6096
                );
6097
                $delete_icon .= '</a>';
6098
6099
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6100
                $previewImage = Display::return_icon(
6101
                    'preview_view.png',
6102
                    get_lang('Preview'),
6103
                    [],
6104
                    ICON_SIZE_TINY
6105
                );
6106
6107
                switch ($arrLP[$i]['item_type']) {
6108
                    case TOOL_DOCUMENT:
6109
                    case TOOL_LP_FINAL_ITEM:
6110
                    case TOOL_READOUT_TEXT:
6111
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6112
                        $previewIcon = Display::url(
6113
                            $previewImage,
6114
                            $urlPreviewLink,
6115
                            [
6116
                                'target' => '_blank',
6117
                                'class' => 'btn btn-default',
6118
                                'data-title' => $arrLP[$i]['title'],
6119
                                'title' => $arrLP[$i]['title'],
6120
                            ]
6121
                        );
6122
                        break;
6123
                    case TOOL_THREAD:
6124
                    case TOOL_FORUM:
6125
                    case TOOL_QUIZ:
6126
                    case TOOL_STUDENTPUBLICATION:
6127
                    case TOOL_LP_FINAL_ITEM:
6128
                    case TOOL_LINK:
6129
                        $class = 'btn btn-default';
6130
                        $target = '_blank';
6131
                        $link = self::rl_get_resource_link_for_learnpath(
6132
                            $this->course_int_id,
6133
                            $this->lp_id,
6134
                            $arrLP[$i]['id'],
6135
                            0
6136
                        );
6137
                        $previewIcon = Display::url(
6138
                            $previewImage,
6139
                            $link,
6140
                            [
6141
                                'class' => $class,
6142
                                'data-title' => $arrLP[$i]['title'],
6143
                                'title' => $arrLP[$i]['title'],
6144
                                'target' => $target,
6145
                            ]
6146
                        );
6147
                        break;
6148
                    default:
6149
                        $previewIcon = Display::url(
6150
                            $previewImage,
6151
                            $url.'&action=view_item',
6152
                            ['class' => 'btn btn-default', 'target' => '_blank']
6153
                        );
6154
                        break;
6155
                }
6156
6157
                if ($arrLP[$i]['item_type'] != 'dir') {
6158
                    $prerequisities_icon = Display::url(
6159
                        Display::return_icon(
6160
                            'accept.png',
6161
                            get_lang('LearnpathPrerequisites'),
6162
                            [],
6163
                            ICON_SIZE_TINY
6164
                        ),
6165
                        $url.'&action=edit_item_prereq',
6166
                        ['class' => 'btn btn-default']
6167
                    );
6168
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6169
                        $move_item_icon = Display::url(
6170
                            Display::return_icon(
6171
                                'move.png',
6172
                                get_lang('Move'),
6173
                                [],
6174
                                ICON_SIZE_TINY
6175
                            ),
6176
                            $url.'&action=move_item',
6177
                            ['class' => 'btn btn-default']
6178
                        );
6179
                    }
6180
                    $audio_icon = Display::url(
6181
                        Display::return_icon(
6182
                            'audio.png',
6183
                            get_lang('UplUpload'),
6184
                            [],
6185
                            ICON_SIZE_TINY
6186
                        ),
6187
                        $url.'&action=add_audio',
6188
                        ['class' => 'btn btn-default']
6189
                    );
6190
                }
6191
            }
6192
            if ($update_audio != 'true') {
6193
                $row = $move_icon.' '.$icon.
6194
                    Display::span($title_cut).
6195
                    Display::tag(
6196
                        'div',
6197
                        "<div class=\"btn-group btn-group-xs\">
6198
                                    $previewIcon 
6199
                                    $audio 
6200
                                    $edit_icon 
6201
                                    $pluginCalendarIcon
6202
                                    $forumIcon 
6203
                                    $prerequisities_icon 
6204
                                    $move_item_icon 
6205
                                    $audio_icon 
6206
                                    $orderIcons
6207
                                    $delete_icon
6208
                                </div>",
6209
                        ['class' => 'btn-toolbar button_actions']
6210
                    );
6211
            } else {
6212
                $row =
6213
                    Display::span($title.$icon).
6214
                    Display::span($audio, ['class' => 'button_actions']);
6215
            }
6216
6217
            $default_data[$arrLP[$i]['id']] = $row;
6218
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6219
6220
            if (empty($parent_id)) {
6221
                $elements[$arrLP[$i]['id']]['data'] = $row;
6222
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6223
            } else {
6224
                $parent_arrays = [];
6225
                if ($arrLP[$i]['depth'] > 1) {
6226
                    //Getting list of parents
6227
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6228
                        foreach ($arrLP as $item) {
6229
                            if ($item['id'] == $parent_id) {
6230
                                if ($item['parent_item_id'] == 0) {
6231
                                    $parent_id = $item['id'];
6232
                                    break;
6233
                                } else {
6234
                                    $parent_id = $item['parent_item_id'];
6235
                                    if (empty($parent_arrays)) {
6236
                                        $parent_arrays[] = intval($item['id']);
6237
                                    }
6238
                                    $parent_arrays[] = $parent_id;
6239
                                    break;
6240
                                }
6241
                            }
6242
                        }
6243
                    }
6244
                }
6245
6246
                if (!empty($parent_arrays)) {
6247
                    $parent_arrays = array_reverse($parent_arrays);
6248
                    $val = '$elements';
6249
                    $x = 0;
6250
                    foreach ($parent_arrays as $item) {
6251
                        if ($x != count($parent_arrays) - 1) {
6252
                            $val .= '["'.$item.'"]["children"]';
6253
                        } else {
6254
                            $val .= '["'.$item.'"]["children"]';
6255
                        }
6256
                        $x++;
6257
                    }
6258
                    $val .= "";
6259
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6260
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6261
                } else {
6262
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6263
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6264
                }
6265
            }
6266
        }
6267
6268
        return [
6269
            'elements' => $elements,
6270
            'default_data' => $default_data,
6271
            'default_content' => $default_content,
6272
            'return_audio' => $return_audio,
6273
        ];
6274
    }
6275
6276
    /**
6277
     * @param string $updateAudio true/false strings
6278
     *
6279
     * @return string
6280
     */
6281
    public function returnLpItemList($updateAudio)
6282
    {
6283
        $result = $this->processBuildMenuElements($updateAudio);
6284
6285
        $html = self::print_recursive(
6286
            $result['elements'],
6287
            $result['default_data'],
6288
            $result['default_content']
6289
        );
6290
6291
        if (!empty($html)) {
6292
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6293
        }
6294
6295
        return $html;
6296
    }
6297
6298
    /**
6299
     * @param string $update_audio
6300
     * @param bool   $drop_element_here
6301
     *
6302
     * @return string
6303
     */
6304
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6305
    {
6306
        $result = $this->processBuildMenuElements($update_audio);
6307
6308
        $list = '<ul id="lp_item_list">';
6309
        $tree = $this->print_recursive(
6310
            $result['elements'],
6311
            $result['default_data'],
6312
            $result['default_content']
6313
        );
6314
6315
        if (!empty($tree)) {
6316
            $list .= $tree;
6317
        } else {
6318
            if ($drop_element_here) {
6319
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6320
            }
6321
        }
6322
        $list .= '</ul>';
6323
6324
        $return = Display::panelCollapse(
6325
            $this->name,
6326
            $list,
6327
            'scorm-list',
6328
            null,
6329
            'scorm-list-accordion',
6330
            'scorm-list-collapse'
6331
        );
6332
6333
        if ($update_audio === 'true') {
6334
            $return = $result['return_audio'];
6335
        }
6336
6337
        return $return;
6338
    }
6339
6340
    /**
6341
     * @param array $elements
6342
     * @param array $default_data
6343
     * @param array $default_content
6344
     *
6345
     * @return string
6346
     */
6347
    public function print_recursive($elements, $default_data, $default_content)
6348
    {
6349
        $return = '';
6350
        foreach ($elements as $key => $item) {
6351
            if (isset($item['load_data']) || empty($item['data'])) {
6352
                $item['data'] = $default_data[$item['load_data']];
6353
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6354
            }
6355
            $sub_list = '';
6356
            if (isset($item['type']) && $item['type'] === 'dir') {
6357
                // empty value
6358
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6359
            }
6360
            if (empty($item['children'])) {
6361
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6362
                $active = null;
6363
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6364
                    $active = 'active';
6365
                }
6366
                $return .= Display::tag(
6367
                    'li',
6368
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6369
                    ['id' => $key, 'class' => 'record li_container']
6370
                );
6371
            } else {
6372
                // Sections
6373
                $data = '';
6374
                if (isset($item['children'])) {
6375
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6376
                }
6377
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6378
                $return .= Display::tag(
6379
                    'li',
6380
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6381
                    ['id' => $key, 'class' => 'record li_container']
6382
                );
6383
            }
6384
        }
6385
6386
        return $return;
6387
    }
6388
6389
    /**
6390
     * This function builds the action menu.
6391
     *
6392
     * @param bool $returnContent          Optional
6393
     * @param bool $showRequirementButtons Optional. Allow show the requirements button
6394
     * @param bool $isConfigPage           Optional. If is the config page, show the edit button
6395
     * @param bool $allowExpand            Optional. Allow show the expand/contract button
6396
     *
6397
     * @return string
6398
     */
6399
    public function build_action_menu(
6400
        $returnContent = false,
6401
        $showRequirementButtons = true,
6402
        $isConfigPage = false,
6403
        $allowExpand = true
6404
    ) {
6405
        $actionsLeft = '';
6406
        $actionsRight = '';
6407
        $actionsLeft .= Display::url(
6408
            Display::return_icon(
6409
                'back.png',
6410
                get_lang('ReturnToLearningPaths'),
6411
                '',
6412
                ICON_SIZE_MEDIUM
6413
            ),
6414
            'lp_controller.php?'.api_get_cidreq()
6415
        );
6416
        $actionsLeft .= Display::url(
6417
            Display::return_icon(
6418
                'preview_view.png',
6419
                get_lang('Preview'),
6420
                '',
6421
                ICON_SIZE_MEDIUM
6422
            ),
6423
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6424
                'action' => 'view',
6425
                'lp_id' => $this->lp_id,
6426
                'isStudentView' => 'true',
6427
            ])
6428
        );
6429
6430
        $actionsLeft .= Display::url(
6431
            Display::return_icon(
6432
                'upload_audio.png',
6433
                get_lang('UpdateAllAudioFragments'),
6434
                '',
6435
                ICON_SIZE_MEDIUM
6436
            ),
6437
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6438
                'action' => 'admin_view',
6439
                'lp_id' => $this->lp_id,
6440
                'updateaudio' => 'true',
6441
            ])
6442
        );
6443
6444
        if (!$isConfigPage) {
6445
            $actionsLeft .= Display::url(
6446
                Display::return_icon(
6447
                    'settings.png',
6448
                    get_lang('CourseSettings'),
6449
                    '',
6450
                    ICON_SIZE_MEDIUM
6451
                ),
6452
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6453
                    'action' => 'edit',
6454
                    'lp_id' => $this->lp_id,
6455
                ])
6456
            );
6457
        } else {
6458
            $actionsLeft .= Display::url(
6459
                Display::return_icon(
6460
                    'edit.png',
6461
                    get_lang('Edit'),
6462
                    '',
6463
                    ICON_SIZE_MEDIUM
6464
                ),
6465
                'lp_controller.php?'.http_build_query([
6466
                    'action' => 'build',
6467
                    'lp_id' => $this->lp_id,
6468
                ]).'&'.api_get_cidreq()
6469
            );
6470
        }
6471
6472
        if ($allowExpand) {
6473
            $actionsLeft .= Display::url(
6474
                Display::return_icon(
6475
                    'expand.png',
6476
                    get_lang('Expand'),
6477
                    ['id' => 'expand'],
6478
                    ICON_SIZE_MEDIUM
6479
                ).
6480
                Display::return_icon(
6481
                    'contract.png',
6482
                    get_lang('Collapse'),
6483
                    ['id' => 'contract', 'class' => 'hide'],
6484
                    ICON_SIZE_MEDIUM
6485
                ),
6486
                '#',
6487
                ['role' => 'button', 'id' => 'hide_bar_template']
6488
            );
6489
        }
6490
6491
        if ($showRequirementButtons) {
6492
            $buttons = [
6493
                [
6494
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6495
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6496
                        'action' => 'set_previous_step_as_prerequisite',
6497
                        'lp_id' => $this->lp_id,
6498
                    ]),
6499
                ],
6500
                [
6501
                    'title' => get_lang('ClearAllPrerequisites'),
6502
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6503
                        'action' => 'clear_prerequisites',
6504
                        'lp_id' => $this->lp_id,
6505
                    ]),
6506
                ],
6507
            ];
6508
            $actionsRight = Display::groupButtonWithDropDown(
6509
                get_lang('PrerequisitesOptions'),
6510
                $buttons,
6511
                true
6512
            );
6513
        }
6514
6515
        $toolbar = Display::toolbarAction(
6516
            'actions-lp-controller',
6517
            [$actionsLeft, $actionsRight]
6518
        );
6519
6520
        if ($returnContent) {
6521
            return $toolbar;
6522
        }
6523
6524
        echo $toolbar;
6525
    }
6526
6527
    /**
6528
     * Creates the default learning path folder.
6529
     *
6530
     * @param array $course
6531
     * @param int   $creatorId
6532
     *
6533
     * @return bool
6534
     */
6535
    public static function generate_learning_path_folder($course, $creatorId = 0)
6536
    {
6537
        // Creating learning_path folder
6538
        $dir = '/learning_path';
6539
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6540
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6541
6542
        $folder = false;
6543
        $folderData = create_unexisting_directory(
6544
            $course,
6545
            $creatorId,
6546
            0,
6547
            null,
6548
            0,
6549
            $filepath,
6550
            $dir,
6551
            get_lang('LearningPaths'),
6552
            0
6553
        );
6554
6555
        if (!empty($folderData)) {
6556
            $folder = true;
6557
        }
6558
6559
        return $folder;
6560
    }
6561
6562
    /**
6563
     * @param array  $course
6564
     * @param string $lp_name
6565
     * @param int    $creatorId
6566
     *
6567
     * @return array
6568
     */
6569
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6570
    {
6571
        $filepath = '';
6572
        $dir = '/learning_path/';
6573
6574
        if (empty($lp_name)) {
6575
            $lp_name = $this->name;
6576
        }
6577
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6578
        $folder = self::generate_learning_path_folder($course, $creatorId);
6579
6580
        // Limits title size
6581
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6582
        $dir = $dir.$title;
6583
6584
        // Creating LP folder
6585
        $documentId = null;
6586
        if ($folder) {
6587
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6588
            $folderData = create_unexisting_directory(
6589
                $course,
6590
                $creatorId,
6591
                0,
6592
                0,
6593
                0,
6594
                $filepath,
6595
                $dir,
6596
                $lp_name
6597
            );
6598
            if (!empty($folderData)) {
6599
                $folder = true;
6600
            }
6601
6602
            $documentId = $folderData->getIid();
6603
            $dir = $dir.'/';
6604
            if ($folder) {
6605
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6606
            }
6607
        }
6608
6609
        if (empty($documentId)) {
6610
            $dir = api_remove_trailing_slash($dir);
6611
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6612
        }
6613
6614
        $array = [
6615
            'dir' => $dir,
6616
            'filepath' => $filepath,
6617
            'folder' => $folder,
6618
            'id' => $documentId,
6619
        ];
6620
6621
        return $array;
6622
    }
6623
6624
    /**
6625
     * Create a new document //still needs some finetuning.
6626
     *
6627
     * @param array  $courseInfo
6628
     * @param string $content
6629
     * @param string $title
6630
     * @param string $extension
6631
     * @param int    $parentId
6632
     * @param int    $creatorId  creator id
6633
     *
6634
     * @return int
6635
     */
6636
    public function create_document(
6637
        $courseInfo,
6638
        $content = '',
6639
        $title = '',
6640
        $extension = 'html',
6641
        $parentId = 0,
6642
        $creatorId = 0
6643
    ) {
6644
        if (!empty($courseInfo)) {
6645
            $course_id = $courseInfo['real_id'];
6646
        } else {
6647
            $course_id = api_get_course_int_id();
6648
        }
6649
6650
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6651
        $sessionId = api_get_session_id();
6652
6653
        // Generates folder
6654
        $result = $this->generate_lp_folder($courseInfo);
6655
        $dir = $result['dir'];
6656
6657
        if (empty($parentId) || $parentId == '/') {
6658
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6659
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6660
6661
            if ($parentId === '/') {
6662
                $dir = '/';
6663
            }
6664
6665
            // Please, do not modify this dirname formatting.
6666
            if (strstr($dir, '..')) {
6667
                $dir = '/';
6668
            }
6669
6670
            if (!empty($dir[0]) && $dir[0] == '.') {
6671
                $dir = substr($dir, 1);
6672
            }
6673
            if (!empty($dir[0]) && $dir[0] != '/') {
6674
                $dir = '/'.$dir;
6675
            }
6676
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6677
                $dir .= '/';
6678
            }
6679
        } else {
6680
            $parentInfo = DocumentManager::get_document_data_by_id(
6681
                $parentId,
6682
                $courseInfo['code']
6683
            );
6684
            if (!empty($parentInfo)) {
6685
                $dir = $parentInfo['path'].'/';
6686
            }
6687
        }
6688
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6689
        // is already escaped twice when it gets here.
6690
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6691
        if (!empty($title)) {
6692
            $title = api_replace_dangerous_char(stripslashes($title));
6693
        } else {
6694
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6695
        }
6696
6697
        $title = disable_dangerous_file($title);
6698
        $filename = $title;
6699
        $content = !empty($content) ? $content : $_POST['content_lp'];
6700
        $tmp_filename = $filename;
6701
        $filename = $tmp_filename.'.'.$extension;
6702
6703
        if ($extension === 'html') {
6704
            $content = stripslashes($content);
6705
            $content = str_replace(
6706
                api_get_path(WEB_COURSE_PATH),
6707
                api_get_path(REL_PATH).'courses/',
6708
                $content
6709
            );
6710
6711
            // Change the path of mp3 to absolute.
6712
            // The first regexp deals with :// urls.
6713
            $content = preg_replace(
6714
                "|(flashvars=\"file=)([^:/]+)/|",
6715
                "$1".api_get_path(
6716
                    REL_COURSE_PATH
6717
                ).$courseInfo['path'].'/document/',
6718
                $content
6719
            );
6720
            // The second regexp deals with audio/ urls.
6721
            $content = preg_replace(
6722
                "|(flashvars=\"file=)([^/]+)/|",
6723
                "$1".api_get_path(
6724
                    REL_COURSE_PATH
6725
                ).$courseInfo['path'].'/document/$2/',
6726
                $content
6727
            );
6728
            // For flv player: To prevent edition problem with firefox,
6729
            // we have to use a strange tip (don't blame me please).
6730
            $content = str_replace(
6731
                '</body>',
6732
                '<style type="text/css">body{}</style></body>',
6733
                $content
6734
            );
6735
        }
6736
6737
        $save_file_path = $dir.$filename;
6738
6739
        $document = DocumentManager::addDocument(
6740
            $courseInfo,
6741
            $save_file_path,
6742
            'file',
6743
            '',
6744
            $tmp_filename,
6745
            '',
6746
            0, //readonly
6747
            true,
6748
            null,
6749
            $sessionId,
6750
            $creatorId,
6751
            false,
6752
            $content,
6753
            $parentId
6754
        );
6755
6756
        $document_id = $document->getIid();
6757
        if ($document_id) {
6758
            $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
6759
            $new_title = $originalTitle;
6760
6761
            if ($new_comment || $new_title) {
6762
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6763
                $ct = '';
6764
                if ($new_comment) {
6765
                    $ct .= ", comment='".Database::escape_string($new_comment)."'";
6766
                }
6767
                if ($new_title) {
6768
                    $ct .= ", title='".Database::escape_string($new_title)."' ";
6769
                }
6770
6771
                $sql = "UPDATE $tbl_doc SET ".substr($ct, 1)."
6772
                        WHERE c_id = $course_id AND id = $document_id ";
6773
                Database::query($sql);
6774
            }
6775
        }
6776
6777
        return $document_id;
6778
    }
6779
6780
    /**
6781
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
6782
     *
6783
     * @param array $_course array
6784
     */
6785
    public function edit_document($_course)
6786
    {
6787
        $course_id = api_get_course_int_id();
6788
        $urlAppend = api_get_configuration_value('url_append');
6789
        // Please, do not modify this dirname formatting.
6790
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
6791
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
6792
6793
        if (strstr($dir, '..')) {
6794
            $dir = '/';
6795
        }
6796
6797
        if (isset($dir[0]) && $dir[0] == '.') {
6798
            $dir = substr($dir, 1);
6799
        }
6800
6801
        if (isset($dir[0]) && $dir[0] != '/') {
6802
            $dir = '/'.$dir;
6803
        }
6804
6805
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6806
            $dir .= '/';
6807
        }
6808
6809
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
6810
        if (!is_dir($filepath)) {
6811
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
6812
        }
6813
6814
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
6815
6816
        if (isset($_POST['path']) && !empty($_POST['path'])) {
6817
            $document_id = (int) $_POST['path'];
6818
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
6819
            if (empty($documentInfo)) {
6820
                // Try with iid
6821
                $table = Database::get_course_table(TABLE_DOCUMENT);
6822
                $sql = "SELECT id, path FROM $table 
6823
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
6824
                $res_doc = Database::query($sql);
6825
                $row = Database::fetch_array($res_doc);
6826
                if ($row) {
6827
                    $document_id = $row['id'];
6828
                    $documentPath = $row['path'];
6829
                }
6830
            } else {
6831
                $documentPath = $documentInfo['path'];
6832
            }
6833
6834
            $content = stripslashes($_POST['content_lp']);
6835
            $file = $filepath.$documentPath;
6836
6837
            if (!file_exists($file)) {
6838
                return false;
6839
            }
6840
6841
            if ($fp = @fopen($file, 'w')) {
6842
                $content = str_replace(
6843
                    api_get_path(WEB_COURSE_PATH),
6844
                    $urlAppend.api_get_path(REL_COURSE_PATH),
6845
                    $content
6846
                );
6847
                // Change the path of mp3 to absolute.
6848
                // The first regexp deals with :// urls.
6849
                $content = preg_replace(
6850
                    "|(flashvars=\"file=)([^:/]+)/|",
6851
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
6852
                    $content
6853
                );
6854
                // The second regexp deals with audio/ urls.
6855
                $content = preg_replace(
6856
                    "|(flashvars=\"file=)([^:/]+)/|",
6857
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
6858
                    $content
6859
                );
6860
                fputs($fp, $content);
6861
                fclose($fp);
6862
6863
                $sql = "UPDATE $table_doc SET
6864
                            title='".Database::escape_string($_POST['title'])."'
6865
                        WHERE c_id = $course_id AND id = ".$document_id;
6866
                Database::query($sql);
6867
            }
6868
        }
6869
    }
6870
6871
    /**
6872
     * Displays the selected item, with a panel for manipulating the item.
6873
     *
6874
     * @param int    $item_id
6875
     * @param string $msg
6876
     * @param bool   $show_actions
6877
     *
6878
     * @return string
6879
     */
6880
    public function display_item($item_id, $msg = null, $show_actions = true)
6881
    {
6882
        $course_id = api_get_course_int_id();
6883
        $return = '';
6884
        if (is_numeric($item_id)) {
6885
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
6886
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
6887
                    WHERE lp.iid = ".intval($item_id);
6888
            $result = Database::query($sql);
6889
            while ($row = Database::fetch_array($result, 'ASSOC')) {
6890
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
6891
6892
                // Prevents wrong parent selection for document, see Bug#1251.
6893
                if ($row['item_type'] != 'dir') {
6894
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
6895
                }
6896
6897
                if ($show_actions) {
6898
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
6899
                }
6900
                $return .= '<div style="padding:10px;">';
6901
6902
                if ($msg != '') {
6903
                    $return .= $msg;
6904
                }
6905
6906
                $return .= '<h3>'.$row['title'].'</h3>';
6907
6908
                switch ($row['item_type']) {
6909
                    case TOOL_THREAD:
6910
                        $link = $this->rl_get_resource_link_for_learnpath(
6911
                            $course_id,
6912
                            $row['lp_id'],
6913
                            $item_id,
6914
                            0
6915
                        );
6916
                        $return .= Display::url(
6917
                            get_lang('GoToThread'),
6918
                            $link,
6919
                            ['class' => 'btn btn-primary']
6920
                        );
6921
                        break;
6922
                    case TOOL_FORUM:
6923
                        $return .= Display::url(
6924
                            get_lang('GoToForum'),
6925
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
6926
                            ['class' => 'btn btn-primary']
6927
                        );
6928
                        break;
6929
                    case TOOL_QUIZ:
6930
                        if (!empty($row['path'])) {
6931
                            $exercise = new Exercise();
6932
                            $exercise->read($row['path']);
6933
                            $return .= $exercise->description.'<br />';
6934
                            $return .= Display::url(
6935
                                get_lang('GoToExercise'),
6936
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
6937
                                ['class' => 'btn btn-primary']
6938
                            );
6939
                        }
6940
                        break;
6941
                    case TOOL_LP_FINAL_ITEM:
6942
                        $return .= $this->getSavedFinalItem();
6943
                        break;
6944
                    case TOOL_DOCUMENT:
6945
                    case TOOL_READOUT_TEXT:
6946
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
6947
                        $sql_doc = "SELECT path FROM $tbl_doc
6948
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
6949
                        $result = Database::query($sql_doc);
6950
                        $path_file = Database::result($result, 0, 0);
6951
                        $path_parts = pathinfo($path_file);
6952
                        // TODO: Correct the following naive comparisons.
6953
                        if (in_array($path_parts['extension'], [
6954
                            'html',
6955
                            'txt',
6956
                            'png',
6957
                            'jpg',
6958
                            'JPG',
6959
                            'jpeg',
6960
                            'JPEG',
6961
                            'gif',
6962
                            'swf',
6963
                            'pdf',
6964
                            'htm',
6965
                        ])) {
6966
                            $return .= $this->display_document($row['path'], true, true);
6967
                        }
6968
                        break;
6969
                    case TOOL_HOTPOTATOES:
6970
                        $return .= $this->display_document($row['path'], false, true);
6971
                        break;
6972
                }
6973
                $return .= '</div>';
6974
            }
6975
        }
6976
6977
        return $return;
6978
    }
6979
6980
    /**
6981
     * Shows the needed forms for editing a specific item.
6982
     *
6983
     * @param int $item_id
6984
     *
6985
     * @throws Exception
6986
     * @throws HTML_QuickForm_Error
6987
     *
6988
     * @return string
6989
     */
6990
    public function display_edit_item($item_id)
6991
    {
6992
        $course_id = api_get_course_int_id();
6993
        $return = '';
6994
        $item_id = (int) $item_id;
6995
6996
        if (empty($item_id)) {
6997
            return '';
6998
        }
6999
7000
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7001
        $sql = "SELECT * FROM $tbl_lp_item
7002
                WHERE iid = ".$item_id;
7003
        $res = Database::query($sql);
7004
        $row = Database::fetch_array($res);
7005
        switch ($row['item_type']) {
7006
            case 'dir':
7007
            case 'asset':
7008
            case 'sco':
7009
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7010
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7011
                    $return .= $this->display_item_form(
7012
                        $row['item_type'],
7013
                        get_lang('EditCurrentChapter').' :',
7014
                        'edit',
7015
                        $item_id,
7016
                        $row
7017
                    );
7018
                } else {
7019
                    $return .= $this->display_item_form(
7020
                        $row['item_type'],
7021
                        get_lang('EditCurrentChapter').' :',
7022
                        'edit_item',
7023
                        $item_id,
7024
                        $row
7025
                    );
7026
                }
7027
                break;
7028
            case TOOL_DOCUMENT:
7029
            case TOOL_READOUT_TEXT:
7030
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7031
                $sql = "SELECT lp.*, doc.path as dir
7032
                        FROM $tbl_lp_item as lp
7033
                        LEFT JOIN $tbl_doc as doc
7034
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7035
                        WHERE
7036
                            doc.c_id = $course_id AND
7037
                            lp.iid = ".$item_id;
7038
                $res_step = Database::query($sql);
7039
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7040
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7041
7042
                if ($row['item_type'] === TOOL_DOCUMENT) {
7043
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7044
                }
7045
7046
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7047
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7048
                }
7049
                break;
7050
            case TOOL_LINK:
7051
                $linkId = (int) $row['path'];
7052
                if (!empty($linkId)) {
7053
                    $table = Database::get_course_table(TABLE_LINK);
7054
                    $sql = 'SELECT url FROM '.$table.'
7055
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7056
                    $res_link = Database::query($sql);
7057
                    $row_link = Database::fetch_array($res_link);
7058
                    if (empty($row_link)) {
7059
                        // Try with id
7060
                        $sql = 'SELECT url FROM '.$table.'
7061
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7062
                        $res_link = Database::query($sql);
7063
                        $row_link = Database::fetch_array($res_link);
7064
                    }
7065
7066
                    if (is_array($row_link)) {
7067
                        $row['url'] = $row_link['url'];
7068
                    }
7069
                }
7070
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7071
                $return .= $this->display_link_form('edit', $item_id, $row);
7072
                break;
7073
            case TOOL_LP_FINAL_ITEM:
7074
                Session::write('finalItem', true);
7075
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7076
                $sql = "SELECT lp.*, doc.path as dir
7077
                        FROM $tbl_lp_item as lp
7078
                        LEFT JOIN $tbl_doc as doc
7079
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7080
                        WHERE
7081
                            doc.c_id = $course_id AND
7082
                            lp.iid = ".$item_id;
7083
                $res_step = Database::query($sql);
7084
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7085
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7086
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7087
                break;
7088
            case TOOL_QUIZ:
7089
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7090
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7091
                break;
7092
            case TOOL_HOTPOTATOES:
7093
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7094
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7095
                break;
7096
            case TOOL_STUDENTPUBLICATION:
7097
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7098
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7099
                break;
7100
            case TOOL_FORUM:
7101
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7102
                $return .= $this->display_forum_form('edit', $item_id, $row);
7103
                break;
7104
            case TOOL_THREAD:
7105
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7106
                $return .= $this->display_thread_form('edit', $item_id, $row);
7107
                break;
7108
        }
7109
7110
        return $return;
7111
    }
7112
7113
    /**
7114
     * Function that displays a list with al the resources that
7115
     * could be added to the learning path.
7116
     *
7117
     * @throws Exception
7118
     * @throws HTML_QuickForm_Error
7119
     *
7120
     * @return bool
7121
     */
7122
    public function display_resources()
7123
    {
7124
        $course_code = api_get_course_id();
7125
7126
        // Get all the docs.
7127
        $documents = $this->get_documents(true);
7128
7129
        // Get all the exercises.
7130
        $exercises = $this->get_exercises();
7131
7132
        // Get all the links.
7133
        $links = $this->get_links();
7134
7135
        // Get all the student publications.
7136
        $works = $this->get_student_publications();
7137
7138
        // Get all the forums.
7139
        $forums = $this->get_forums(null, $course_code);
7140
7141
        // Get the final item form (see BT#11048) .
7142
        $finish = $this->getFinalItemForm();
7143
7144
        $headers = [
7145
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7146
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7147
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7148
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7149
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7150
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7151
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7152
        ];
7153
7154
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7155
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7156
7157
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7158
7159
        echo Display::tabs(
7160
            $headers,
7161
            [
7162
                $documents,
7163
                $exercises,
7164
                $links,
7165
                $works,
7166
                $forums,
7167
                $dir,
7168
                $finish,
7169
            ],
7170
            'resource_tab',
7171
            [],
7172
            [],
7173
            $selected
7174
        );
7175
7176
        return true;
7177
    }
7178
7179
    /**
7180
     * Returns the extension of a document.
7181
     *
7182
     * @param string $filename
7183
     *
7184
     * @return string Extension (part after the last dot)
7185
     */
7186
    public function get_extension($filename)
7187
    {
7188
        $explode = explode('.', $filename);
7189
7190
        return $explode[count($explode) - 1];
7191
    }
7192
7193
    /**
7194
     * Displays a document by id.
7195
     *
7196
     * @param int  $id
7197
     * @param bool $show_title
7198
     * @param bool $iframe
7199
     * @param bool $edit_link
7200
     *
7201
     * @return string
7202
     */
7203
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7204
    {
7205
        $_course = api_get_course_info();
7206
        $course_id = api_get_course_int_id();
7207
        $id = (int) $id;
7208
        $return = '';
7209
        $table = Database::get_course_table(TABLE_DOCUMENT);
7210
        $sql_doc = "SELECT * FROM $table
7211
                    WHERE c_id = $course_id AND iid = $id";
7212
        $res_doc = Database::query($sql_doc);
7213
        $row_doc = Database::fetch_array($res_doc);
7214
7215
        // TODO: Add a path filter.
7216
        if ($iframe) {
7217
            $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>';
7218
        } else {
7219
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7220
        }
7221
7222
        return $return;
7223
    }
7224
7225
    /**
7226
     * Return HTML form to add/edit a quiz.
7227
     *
7228
     * @param string $action     Action (add/edit)
7229
     * @param int    $id         Item ID if already exists
7230
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7231
     *
7232
     * @throws Exception
7233
     *
7234
     * @return string HTML form
7235
     */
7236
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7237
    {
7238
        $course_id = api_get_course_int_id();
7239
        $id = (int) $id;
7240
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7241
7242
        if ($id != 0 && is_array($extra_info)) {
7243
            $item_title = $extra_info['title'];
7244
            $item_description = $extra_info['description'];
7245
        } elseif (is_numeric($extra_info)) {
7246
            $sql = "SELECT title, description
7247
                    FROM $tbl_quiz
7248
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7249
7250
            $result = Database::query($sql);
7251
            $row = Database::fetch_array($result);
7252
            $item_title = $row['title'];
7253
            $item_description = $row['description'];
7254
        } else {
7255
            $item_title = '';
7256
            $item_description = '';
7257
        }
7258
        $item_title = Security::remove_XSS($item_title);
7259
        $item_description = Security::remove_XSS($item_description);
7260
7261
        $parent = 0;
7262
        if ($id != 0 && is_array($extra_info)) {
7263
            $parent = $extra_info['parent_item_id'];
7264
        }
7265
7266
        $arrLP = $this->getItemsForForm();
7267
        $this->tree_array($arrLP);
7268
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7269
        unset($this->arrMenu);
7270
7271
        $form = new FormValidator(
7272
            'quiz_form',
7273
            'POST',
7274
            $this->getCurrentBuildingModeURL()
7275
        );
7276
        $defaults = [];
7277
7278
        if ($action === 'add') {
7279
            $legend = get_lang('CreateTheExercise');
7280
        } elseif ($action === 'move') {
7281
            $legend = get_lang('MoveTheCurrentExercise');
7282
        } else {
7283
            $legend = get_lang('EditCurrentExecice');
7284
        }
7285
7286
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7287
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7288
        }
7289
7290
        $form->addHeader($legend);
7291
7292
        if ($action != 'move') {
7293
            $this->setItemTitle($form);
7294
            $defaults['title'] = $item_title;
7295
        }
7296
7297
        // Select for Parent item, root or chapter
7298
        $selectParent = $form->addSelect(
7299
            'parent',
7300
            get_lang('Parent'),
7301
            [],
7302
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7303
        );
7304
        $selectParent->addOption($this->name, 0);
7305
7306
        $arrHide = [
7307
            $id,
7308
        ];
7309
        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...
7310
            if ($action != 'add') {
7311
                if (
7312
                    ($arrLP[$i]['item_type'] == 'dir') &&
7313
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7314
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7315
                ) {
7316
                    $selectParent->addOption(
7317
                        $arrLP[$i]['title'],
7318
                        $arrLP[$i]['id'],
7319
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7320
                    );
7321
7322
                    if ($parent == $arrLP[$i]['id']) {
7323
                        $selectParent->setSelected($arrLP[$i]['id']);
7324
                    }
7325
                } else {
7326
                    $arrHide[] = $arrLP[$i]['id'];
7327
                }
7328
            } else {
7329
                if ($arrLP[$i]['item_type'] == 'dir') {
7330
                    $selectParent->addOption(
7331
                        $arrLP[$i]['title'],
7332
                        $arrLP[$i]['id'],
7333
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7334
                    );
7335
7336
                    if ($parent == $arrLP[$i]['id']) {
7337
                        $selectParent->setSelected($arrLP[$i]['id']);
7338
                    }
7339
                }
7340
            }
7341
        }
7342
7343
        if (is_array($arrLP)) {
7344
            reset($arrLP);
7345
        }
7346
7347
        $selectPrevious = $form->addSelect(
7348
            'previous',
7349
            get_lang('Position'),
7350
            [],
7351
            ['id' => 'previous']
7352
        );
7353
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7354
7355
        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...
7356
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7357
                $arrLP[$i]['id'] != $id
7358
            ) {
7359
                $selectPrevious->addOption(
7360
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7361
                    $arrLP[$i]['id']
7362
                );
7363
7364
                if (is_array($extra_info)) {
7365
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7366
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7367
                    }
7368
                } elseif ($action == 'add') {
7369
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7370
                }
7371
            }
7372
        }
7373
7374
        if ($action != 'move') {
7375
            $arrHide = [];
7376
            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...
7377
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7378
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7379
                }
7380
            }
7381
        }
7382
7383
        if ($action === 'add') {
7384
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7385
        } else {
7386
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7387
        }
7388
7389
        if ($action === 'move') {
7390
            $form->addHidden('title', $item_title);
7391
            $form->addHidden('description', $item_description);
7392
        }
7393
7394
        if (is_numeric($extra_info)) {
7395
            $form->addHidden('path', $extra_info);
7396
        } elseif (is_array($extra_info)) {
7397
            $form->addHidden('path', $extra_info['path']);
7398
        }
7399
7400
        $form->addHidden('type', TOOL_QUIZ);
7401
        $form->addHidden('post_time', time());
7402
        $form->setDefaults($defaults);
7403
7404
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7405
    }
7406
7407
    /**
7408
     * Addition of Hotpotatoes tests.
7409
     *
7410
     * @param string $action
7411
     * @param int    $id         Internal ID of the item
7412
     * @param string $extra_info
7413
     *
7414
     * @return string HTML structure to display the hotpotatoes addition formular
7415
     */
7416
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7417
    {
7418
        $course_id = api_get_course_int_id();
7419
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
7420
7421
        if ($id != 0 && is_array($extra_info)) {
7422
            $item_title = stripslashes($extra_info['title']);
7423
            $item_description = stripslashes($extra_info['description']);
7424
        } elseif (is_numeric($extra_info)) {
7425
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7426
7427
            $sql = "SELECT * FROM $TBL_DOCUMENT
7428
                    WHERE
7429
                        c_id = $course_id AND
7430
                        path LIKE '".$uploadPath."/%/%htm%' AND
7431
                        iid = ".(int) $extra_info."
7432
                    ORDER BY iid ASC";
7433
7434
            $res_hot = Database::query($sql);
7435
            $row = Database::fetch_array($res_hot);
7436
7437
            $item_title = $row['title'];
7438
            $item_description = $row['description'];
7439
7440
            if (!empty($row['comment'])) {
7441
                $item_title = $row['comment'];
7442
            }
7443
        } else {
7444
            $item_title = '';
7445
            $item_description = '';
7446
        }
7447
7448
        $parent = 0;
7449
        if ($id != 0 && is_array($extra_info)) {
7450
            $parent = $extra_info['parent_item_id'];
7451
        }
7452
7453
        $arrLP = $this->getItemsForForm();
7454
        $legend = '<legend>';
7455
        if ($action == 'add') {
7456
            $legend .= get_lang('CreateTheExercise');
7457
        } elseif ($action == 'move') {
7458
            $legend .= get_lang('MoveTheCurrentExercise');
7459
        } else {
7460
            $legend .= get_lang('EditCurrentExecice');
7461
        }
7462
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7463
            $legend .= Display:: return_message(
7464
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7465
            );
7466
        }
7467
        $legend .= '</legend>';
7468
7469
        $return = '<form method="POST">';
7470
        $return .= $legend;
7471
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7472
        $return .= '<tr>';
7473
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7474
        $return .= '<td class="input">';
7475
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7476
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7477
        $arrHide = [$id];
7478
7479
        if (count($arrLP) > 0) {
7480
            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...
7481
                if ($action != 'add') {
7482
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7483
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7484
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7485
                    ) {
7486
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7487
                    } else {
7488
                        $arrHide[] = $arrLP[$i]['id'];
7489
                    }
7490
                } else {
7491
                    if ($arrLP[$i]['item_type'] == 'dir') {
7492
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7493
                    }
7494
                }
7495
            }
7496
            reset($arrLP);
7497
        }
7498
7499
        $return .= '</select>';
7500
        $return .= '</td>';
7501
        $return .= '</tr>';
7502
        $return .= '<tr>';
7503
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7504
        $return .= '<td class="input">';
7505
        $return .= '<select id="previous" name="previous" size="1">';
7506
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7507
7508
        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...
7509
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7510
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7511
                    $selected = 'selected="selected" ';
7512
                } elseif ($action == 'add') {
7513
                    $selected = 'selected="selected" ';
7514
                } else {
7515
                    $selected = '';
7516
                }
7517
7518
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7519
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7520
            }
7521
        }
7522
7523
        $return .= '</select>';
7524
        $return .= '</td>';
7525
        $return .= '</tr>';
7526
7527
        if ($action != 'move') {
7528
            $return .= '<tr>';
7529
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7530
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7531
            $return .= '</tr>';
7532
            $id_prerequisite = 0;
7533
            if (is_array($arrLP) && count($arrLP) > 0) {
7534
                foreach ($arrLP as $key => $value) {
7535
                    if ($value['id'] == $id) {
7536
                        $id_prerequisite = $value['prerequisite'];
7537
                        break;
7538
                    }
7539
                }
7540
7541
                $arrHide = [];
7542
                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...
7543
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7544
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7545
                    }
7546
                }
7547
            }
7548
        }
7549
7550
        $return .= '<tr>';
7551
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7552
            get_lang('SaveHotpotatoes').'</button></td>';
7553
        $return .= '</tr>';
7554
        $return .= '</table>';
7555
7556
        if ($action == 'move') {
7557
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7558
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7559
        }
7560
7561
        if (is_numeric($extra_info)) {
7562
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7563
        } elseif (is_array($extra_info)) {
7564
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7565
        }
7566
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7567
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7568
        $return .= '</form>';
7569
7570
        return $return;
7571
    }
7572
7573
    /**
7574
     * Return the form to display the forum edit/add option.
7575
     *
7576
     * @param string $action
7577
     * @param int    $id         ID of the lp_item if already exists
7578
     * @param string $extra_info
7579
     *
7580
     * @throws Exception
7581
     *
7582
     * @return string HTML form
7583
     */
7584
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7585
    {
7586
        $course_id = api_get_course_int_id();
7587
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7588
7589
        $item_title = '';
7590
        $item_description = '';
7591
7592
        if ($id != 0 && is_array($extra_info)) {
7593
            $item_title = stripslashes($extra_info['title']);
7594
        } elseif (is_numeric($extra_info)) {
7595
            $sql = "SELECT forum_title as title, forum_comment as comment
7596
                    FROM $tbl_forum
7597
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7598
7599
            $result = Database::query($sql);
7600
            $row = Database::fetch_array($result);
7601
7602
            $item_title = $row['title'];
7603
            $item_description = $row['comment'];
7604
        }
7605
        $parent = 0;
7606
        if ($id != 0 && is_array($extra_info)) {
7607
            $parent = $extra_info['parent_item_id'];
7608
        }
7609
        $arrLP = $this->getItemsForForm();
7610
        $this->tree_array($arrLP);
7611
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7612
        unset($this->arrMenu);
7613
7614
        if ($action == 'add') {
7615
            $legend = get_lang('CreateTheForum');
7616
        } elseif ($action == 'move') {
7617
            $legend = get_lang('MoveTheCurrentForum');
7618
        } else {
7619
            $legend = get_lang('EditCurrentForum');
7620
        }
7621
7622
        $form = new FormValidator(
7623
            'forum_form',
7624
            'POST',
7625
            $this->getCurrentBuildingModeURL()
7626
        );
7627
        $defaults = [];
7628
7629
        $form->addHeader($legend);
7630
7631
        if ($action != 'move') {
7632
            $this->setItemTitle($form);
7633
            $defaults['title'] = $item_title;
7634
        }
7635
7636
        $selectParent = $form->addSelect(
7637
            'parent',
7638
            get_lang('Parent'),
7639
            [],
7640
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7641
        );
7642
        $selectParent->addOption($this->name, 0);
7643
        $arrHide = [
7644
            $id,
7645
        ];
7646
        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...
7647
            if ($action != 'add') {
7648
                if ($arrLP[$i]['item_type'] == 'dir' &&
7649
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7650
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7651
                ) {
7652
                    $selectParent->addOption(
7653
                        $arrLP[$i]['title'],
7654
                        $arrLP[$i]['id'],
7655
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7656
                    );
7657
7658
                    if ($parent == $arrLP[$i]['id']) {
7659
                        $selectParent->setSelected($arrLP[$i]['id']);
7660
                    }
7661
                } else {
7662
                    $arrHide[] = $arrLP[$i]['id'];
7663
                }
7664
            } else {
7665
                if ($arrLP[$i]['item_type'] == 'dir') {
7666
                    $selectParent->addOption(
7667
                        $arrLP[$i]['title'],
7668
                        $arrLP[$i]['id'],
7669
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7670
                    );
7671
7672
                    if ($parent == $arrLP[$i]['id']) {
7673
                        $selectParent->setSelected($arrLP[$i]['id']);
7674
                    }
7675
                }
7676
            }
7677
        }
7678
7679
        if (is_array($arrLP)) {
7680
            reset($arrLP);
7681
        }
7682
7683
        $selectPrevious = $form->addSelect(
7684
            'previous',
7685
            get_lang('Position'),
7686
            [],
7687
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7688
        );
7689
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7690
7691
        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...
7692
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7693
                $arrLP[$i]['id'] != $id
7694
            ) {
7695
                $selectPrevious->addOption(
7696
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7697
                    $arrLP[$i]['id']
7698
                );
7699
7700
                if (isset($extra_info['previous_item_id']) &&
7701
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7702
                ) {
7703
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7704
                } elseif ($action == 'add') {
7705
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7706
                }
7707
            }
7708
        }
7709
7710
        if ($action != 'move') {
7711
            $id_prerequisite = 0;
7712
            if (is_array($arrLP)) {
7713
                foreach ($arrLP as $key => $value) {
7714
                    if ($value['id'] == $id) {
7715
                        $id_prerequisite = $value['prerequisite'];
7716
                        break;
7717
                    }
7718
                }
7719
            }
7720
7721
            $arrHide = [];
7722
            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...
7723
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7724
                    if (isset($extra_info['previous_item_id']) &&
7725
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7726
                    ) {
7727
                        $s_selected_position = $arrLP[$i]['id'];
7728
                    } elseif ($action == 'add') {
7729
                        $s_selected_position = 0;
7730
                    }
7731
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7732
                }
7733
            }
7734
        }
7735
7736
        if ($action == 'add') {
7737
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
7738
        } else {
7739
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
7740
        }
7741
7742
        if ($action == 'move') {
7743
            $form->addHidden('title', $item_title);
7744
            $form->addHidden('description', $item_description);
7745
        }
7746
7747
        if (is_numeric($extra_info)) {
7748
            $form->addHidden('path', $extra_info);
7749
        } elseif (is_array($extra_info)) {
7750
            $form->addHidden('path', $extra_info['path']);
7751
        }
7752
        $form->addHidden('type', TOOL_FORUM);
7753
        $form->addHidden('post_time', time());
7754
        $form->setDefaults($defaults);
7755
7756
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7757
    }
7758
7759
    /**
7760
     * Return HTML form to add/edit forum threads.
7761
     *
7762
     * @param string $action
7763
     * @param int    $id         Item ID if already exists in learning path
7764
     * @param string $extra_info
7765
     *
7766
     * @throws Exception
7767
     *
7768
     * @return string HTML form
7769
     */
7770
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
7771
    {
7772
        $course_id = api_get_course_int_id();
7773
        if (empty($course_id)) {
7774
            return null;
7775
        }
7776
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
7777
7778
        $item_title = '';
7779
        $item_description = '';
7780
        if ($id != 0 && is_array($extra_info)) {
7781
            $item_title = stripslashes($extra_info['title']);
7782
        } elseif (is_numeric($extra_info)) {
7783
            $sql = "SELECT thread_title as title FROM $tbl_forum
7784
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
7785
7786
            $result = Database::query($sql);
7787
            $row = Database::fetch_array($result);
7788
7789
            $item_title = $row['title'];
7790
            $item_description = '';
7791
        }
7792
7793
        $parent = 0;
7794
        if ($id != 0 && is_array($extra_info)) {
7795
            $parent = $extra_info['parent_item_id'];
7796
        }
7797
7798
        $arrLP = $this->getItemsForForm();
7799
        $this->tree_array($arrLP);
7800
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7801
        unset($this->arrMenu);
7802
7803
        $form = new FormValidator(
7804
            'thread_form',
7805
            'POST',
7806
            $this->getCurrentBuildingModeURL()
7807
        );
7808
        $defaults = [];
7809
7810
        if ($action == 'add') {
7811
            $legend = get_lang('CreateTheForum');
7812
        } elseif ($action == 'move') {
7813
            $legend = get_lang('MoveTheCurrentForum');
7814
        } else {
7815
            $legend = get_lang('EditCurrentForum');
7816
        }
7817
7818
        $form->addHeader($legend);
7819
        $selectParent = $form->addSelect(
7820
            'parent',
7821
            get_lang('Parent'),
7822
            [],
7823
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7824
        );
7825
        $selectParent->addOption($this->name, 0);
7826
7827
        $arrHide = [
7828
            $id,
7829
        ];
7830
7831
        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...
7832
            if ($action != 'add') {
7833
                if (
7834
                    ($arrLP[$i]['item_type'] == 'dir') &&
7835
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7836
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7837
                ) {
7838
                    $selectParent->addOption(
7839
                        $arrLP[$i]['title'],
7840
                        $arrLP[$i]['id'],
7841
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7842
                    );
7843
7844
                    if ($parent == $arrLP[$i]['id']) {
7845
                        $selectParent->setSelected($arrLP[$i]['id']);
7846
                    }
7847
                } else {
7848
                    $arrHide[] = $arrLP[$i]['id'];
7849
                }
7850
            } else {
7851
                if ($arrLP[$i]['item_type'] == 'dir') {
7852
                    $selectParent->addOption(
7853
                        $arrLP[$i]['title'],
7854
                        $arrLP[$i]['id'],
7855
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7856
                    );
7857
7858
                    if ($parent == $arrLP[$i]['id']) {
7859
                        $selectParent->setSelected($arrLP[$i]['id']);
7860
                    }
7861
                }
7862
            }
7863
        }
7864
7865
        if ($arrLP != null) {
7866
            reset($arrLP);
7867
        }
7868
7869
        $selectPrevious = $form->addSelect(
7870
            'previous',
7871
            get_lang('Position'),
7872
            [],
7873
            ['id' => 'previous']
7874
        );
7875
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7876
7877
        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...
7878
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7879
                $selectPrevious->addOption(
7880
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7881
                    $arrLP[$i]['id']
7882
                );
7883
7884
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7885
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7886
                } elseif ($action == 'add') {
7887
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7888
                }
7889
            }
7890
        }
7891
7892
        if ($action != 'move') {
7893
            $this->setItemTitle($form);
7894
            $defaults['title'] = $item_title;
7895
7896
            $id_prerequisite = 0;
7897
            if ($arrLP != null) {
7898
                foreach ($arrLP as $key => $value) {
7899
                    if ($value['id'] == $id) {
7900
                        $id_prerequisite = $value['prerequisite'];
7901
                        break;
7902
                    }
7903
                }
7904
            }
7905
7906
            $arrHide = [];
7907
            $s_selected_position = 0;
7908
            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...
7909
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7910
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7911
                        $s_selected_position = $arrLP[$i]['id'];
7912
                    } elseif ($action == 'add') {
7913
                        $s_selected_position = 0;
7914
                    }
7915
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7916
                }
7917
            }
7918
7919
            $selectPrerequisites = $form->addSelect(
7920
                'prerequisites',
7921
                get_lang('LearnpathPrerequisites'),
7922
                [],
7923
                ['id' => 'prerequisites']
7924
            );
7925
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
7926
7927
            foreach ($arrHide as $key => $value) {
7928
                $selectPrerequisites->addOption($value['value'], $key);
7929
7930
                if ($key == $s_selected_position && $action == 'add') {
7931
                    $selectPrerequisites->setSelected($key);
7932
                } elseif ($key == $id_prerequisite && $action == 'edit') {
7933
                    $selectPrerequisites->setSelected($key);
7934
                }
7935
            }
7936
        }
7937
7938
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
7939
7940
        if ($action == 'move') {
7941
            $form->addHidden('title', $item_title);
7942
            $form->addHidden('description', $item_description);
7943
        }
7944
7945
        if (is_numeric($extra_info)) {
7946
            $form->addHidden('path', $extra_info);
7947
        } elseif (is_array($extra_info)) {
7948
            $form->addHidden('path', $extra_info['path']);
7949
        }
7950
7951
        $form->addHidden('type', TOOL_THREAD);
7952
        $form->addHidden('post_time', time());
7953
        $form->setDefaults($defaults);
7954
7955
        return $form->returnForm();
7956
    }
7957
7958
    /**
7959
     * Return the HTML form to display an item (generally a dir item).
7960
     *
7961
     * @param string $item_type
7962
     * @param string $title
7963
     * @param string $action
7964
     * @param int    $id
7965
     * @param string $extra_info
7966
     *
7967
     * @throws Exception
7968
     * @throws HTML_QuickForm_Error
7969
     *
7970
     * @return string HTML form
7971
     */
7972
    public function display_item_form(
7973
        $item_type,
7974
        $title = '',
7975
        $action = 'add_item',
7976
        $id = 0,
7977
        $extra_info = 'new'
7978
    ) {
7979
        $_course = api_get_course_info();
7980
7981
        global $charset;
7982
7983
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7984
        $item_title = '';
7985
        $item_description = '';
7986
        $item_path_fck = '';
7987
7988
        if ($id != 0 && is_array($extra_info)) {
7989
            $item_title = $extra_info['title'];
7990
            $item_description = $extra_info['description'];
7991
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
7992
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
7993
        }
7994
        $parent = 0;
7995
        if ($id != 0 && is_array($extra_info)) {
7996
            $parent = $extra_info['parent_item_id'];
7997
        }
7998
7999
        $id = (int) $id;
8000
        $sql = "SELECT * FROM $tbl_lp_item
8001
                WHERE
8002
                    lp_id = ".$this->lp_id." AND
8003
                    iid != $id";
8004
8005
        if ($item_type == 'dir') {
8006
            $sql .= " AND parent_item_id = 0";
8007
        }
8008
8009
        $result = Database::query($sql);
8010
        $arrLP = [];
8011
        while ($row = Database::fetch_array($result)) {
8012
            $arrLP[] = [
8013
                'id' => $row['iid'],
8014
                'item_type' => $row['item_type'],
8015
                'title' => $this->cleanItemTitle($row['title']),
8016
                'title_raw' => $row['title'],
8017
                'path' => $row['path'],
8018
                'description' => $row['description'],
8019
                'parent_item_id' => $row['parent_item_id'],
8020
                'previous_item_id' => $row['previous_item_id'],
8021
                'next_item_id' => $row['next_item_id'],
8022
                'max_score' => $row['max_score'],
8023
                'min_score' => $row['min_score'],
8024
                'mastery_score' => $row['mastery_score'],
8025
                'prerequisite' => $row['prerequisite'],
8026
                'display_order' => $row['display_order'],
8027
            ];
8028
        }
8029
8030
        $this->tree_array($arrLP);
8031
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8032
        unset($this->arrMenu);
8033
8034
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8035
8036
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8037
        $defaults['title'] = api_html_entity_decode(
8038
            $item_title,
8039
            ENT_QUOTES,
8040
            $charset
8041
        );
8042
        $defaults['description'] = $item_description;
8043
8044
        $form->addHeader($title);
8045
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8046
        $arrHide[0]['padding'] = 20;
8047
        $charset = api_get_system_encoding();
8048
        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...
8049
            if ($action != 'add') {
8050
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8051
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8052
                ) {
8053
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8054
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8055
                    if ($parent == $arrLP[$i]['id']) {
8056
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8057
                    }
8058
                }
8059
            } else {
8060
                if ($arrLP[$i]['item_type'] === 'dir') {
8061
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8062
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8063
                    if ($parent == $arrLP[$i]['id']) {
8064
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8065
                    }
8066
                }
8067
            }
8068
        }
8069
8070
        if ($action != 'move') {
8071
            $this->setItemTitle($form);
8072
        } else {
8073
            $form->addElement('hidden', 'title');
8074
        }
8075
8076
        $parentSelect = $form->addElement(
8077
            'select',
8078
            'parent',
8079
            get_lang('Parent'),
8080
            '',
8081
            [
8082
                'id' => 'idParent',
8083
                'onchange' => 'javascript: load_cbo(this.value);',
8084
            ]
8085
        );
8086
8087
        foreach ($arrHide as $key => $value) {
8088
            $parentSelect->addOption(
8089
                $value['value'],
8090
                $key,
8091
                'style="padding-left:'.$value['padding'].'px;"'
8092
            );
8093
            $lastPosition = $key;
8094
        }
8095
8096
        if (!empty($s_selected_parent)) {
8097
            $parentSelect->setSelected($s_selected_parent);
8098
        }
8099
8100
        if (is_array($arrLP)) {
8101
            reset($arrLP);
8102
        }
8103
8104
        $arrHide = [];
8105
        // POSITION
8106
        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...
8107
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8108
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8109
                //this is the same!
8110
                if (isset($extra_info['previous_item_id']) &&
8111
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8112
                ) {
8113
                    $s_selected_position = $arrLP[$i]['id'];
8114
                } elseif ($action == 'add') {
8115
                    $s_selected_position = $arrLP[$i]['id'];
8116
                }
8117
8118
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8119
            }
8120
        }
8121
8122
        $position = $form->addElement(
8123
            'select',
8124
            'previous',
8125
            get_lang('Position'),
8126
            '',
8127
            ['id' => 'previous']
8128
        );
8129
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8130
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8131
8132
        $lastPosition = null;
8133
        foreach ($arrHide as $key => $value) {
8134
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8135
            $lastPosition = $key;
8136
        }
8137
8138
        if (!empty($s_selected_position)) {
8139
            $position->setSelected($s_selected_position);
8140
        }
8141
8142
        // When new chapter add at the end
8143
        if ($action === 'add_item') {
8144
            $position->setSelected($lastPosition);
8145
        }
8146
8147
        if (is_array($arrLP)) {
8148
            reset($arrLP);
8149
        }
8150
8151
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8152
8153
        //fix in order to use the tab
8154
        if ($item_type === 'dir') {
8155
            $form->addElement('hidden', 'type', 'dir');
8156
        }
8157
8158
        $extension = null;
8159
        if (!empty($item_path)) {
8160
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8161
        }
8162
8163
        //assets can't be modified
8164
        //$item_type == 'asset' ||
8165
        if (($item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) {
8166
            if ($item_type == 'sco') {
8167
                $form->addElement(
8168
                    'html',
8169
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8170
                );
8171
            }
8172
            $renderer = $form->defaultRenderer();
8173
            $renderer->setElementTemplate(
8174
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8175
                'content_lp'
8176
            );
8177
8178
            $relative_prefix = '';
8179
            $editor_config = [
8180
                'ToolbarSet' => 'LearningPathDocuments',
8181
                'Width' => '100%',
8182
                'Height' => '500',
8183
                'FullPage' => true,
8184
                'CreateDocumentDir' => $relative_prefix,
8185
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8186
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8187
            ];
8188
8189
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8190
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8191
            $defaults['content_lp'] = file_get_contents($content_path);
8192
        }
8193
8194
        if (!empty($id)) {
8195
            $form->addHidden('id', $id);
8196
        }
8197
8198
        $form->addElement('hidden', 'type', $item_type);
8199
        $form->addElement('hidden', 'post_time', time());
8200
        $form->setDefaults($defaults);
8201
8202
        return $form->returnForm();
8203
    }
8204
8205
    /**
8206
     * @return string
8207
     */
8208
    public function getCurrentBuildingModeURL()
8209
    {
8210
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8211
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8212
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8213
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8214
8215
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8216
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8217
8218
        return $currentUrl;
8219
    }
8220
8221
    /**
8222
     * Returns the form to update or create a document.
8223
     *
8224
     * @param string $action     (add/edit)
8225
     * @param int    $id         ID of the lp_item (if already exists)
8226
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8227
     *
8228
     * @throws Exception
8229
     * @throws HTML_QuickForm_Error
8230
     *
8231
     * @return string HTML form
8232
     */
8233
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8234
    {
8235
        $course_id = api_get_course_int_id();
8236
        $_course = api_get_course_info();
8237
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8238
8239
        $no_display_edit_textarea = false;
8240
        $item_description = '';
8241
        //If action==edit document
8242
        //We don't display the document form if it's not an editable document (html or txt file)
8243
        if ($action === 'edit') {
8244
            if (is_array($extra_info)) {
8245
                $path_parts = pathinfo($extra_info['dir']);
8246
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8247
                    $no_display_edit_textarea = true;
8248
                }
8249
            }
8250
        }
8251
        $no_display_add = false;
8252
8253
        // If action==add an existing document
8254
        // We don't display the document form if it's not an editable document (html or txt file).
8255
        if ($action === 'add') {
8256
            if (is_numeric($extra_info)) {
8257
                $extra_info = (int) $extra_info;
8258
                $sql_doc = "SELECT path FROM $tbl_doc 
8259
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8260
                $result = Database::query($sql_doc);
8261
                $path_file = Database::result($result, 0, 0);
8262
                $path_parts = pathinfo($path_file);
8263
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8264
                    $no_display_add = true;
8265
                }
8266
            }
8267
        }
8268
8269
        $item_title = '';
8270
        $item_description = '';
8271
        if ($id != 0 && is_array($extra_info)) {
8272
            $item_title = stripslashes($extra_info['title']);
8273
            $item_description = stripslashes($extra_info['description']);
8274
            if (empty($item_title)) {
8275
                $path_parts = pathinfo($extra_info['path']);
8276
                $item_title = stripslashes($path_parts['filename']);
8277
            }
8278
        } elseif (is_numeric($extra_info)) {
8279
            $sql = "SELECT path, title FROM $tbl_doc
8280
                    WHERE
8281
                        c_id = ".$course_id." AND
8282
                        iid = ".intval($extra_info);
8283
            $result = Database::query($sql);
8284
            $row = Database::fetch_array($result);
8285
            $item_title = $row['title'];
8286
            $item_title = str_replace('_', ' ', $item_title);
8287
            if (empty($item_title)) {
8288
                $path_parts = pathinfo($row['path']);
8289
                $item_title = stripslashes($path_parts['filename']);
8290
            }
8291
        }
8292
8293
        $return = '<legend>';
8294
        $parent = 0;
8295
        if ($id != 0 && is_array($extra_info)) {
8296
            $parent = $extra_info['parent_item_id'];
8297
        }
8298
8299
        $arrLP = $this->getItemsForForm();
8300
        $this->tree_array($arrLP);
8301
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8302
        unset($this->arrMenu);
8303
8304
        if ($action == 'add') {
8305
            $return .= get_lang('CreateTheDocument');
8306
        } elseif ($action == 'move') {
8307
            $return .= get_lang('MoveTheCurrentDocument');
8308
        } else {
8309
            $return .= get_lang('EditTheCurrentDocument');
8310
        }
8311
        $return .= '</legend>';
8312
8313
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
8314
            $return .= Display::return_message(
8315
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8316
                false
8317
            );
8318
        }
8319
        $form = new FormValidator(
8320
            'form',
8321
            'POST',
8322
            $this->getCurrentBuildingModeURL(),
8323
            '',
8324
            ['enctype' => 'multipart/form-data']
8325
        );
8326
        $defaults['title'] = Security::remove_XSS($item_title);
8327
        if (empty($item_title)) {
8328
            $defaults['title'] = Security::remove_XSS($item_title);
8329
        }
8330
        $defaults['description'] = $item_description;
8331
        $form->addElement('html', $return);
8332
8333
        if ($action != 'move') {
8334
            $data = $this->generate_lp_folder($_course);
8335
            if ($action != 'edit') {
8336
                $folders = DocumentManager::get_all_document_folders(
8337
                    $_course,
8338
                    0,
8339
                    true
8340
                );
8341
                DocumentManager::build_directory_selector(
8342
                    $folders,
8343
                    '',
8344
                    [],
8345
                    true,
8346
                    $form,
8347
                    'directory_parent_id'
8348
                );
8349
            }
8350
8351
            if (isset($data['id'])) {
8352
                $defaults['directory_parent_id'] = $data['id'];
8353
            }
8354
            $this->setItemTitle($form);
8355
        }
8356
8357
        $arrHide[0]['value'] = $this->name;
8358
        $arrHide[0]['padding'] = 20;
8359
8360
        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...
8361
            if ($action != 'add') {
8362
                if ($arrLP[$i]['item_type'] == 'dir' &&
8363
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8364
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8365
                ) {
8366
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8367
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8368
                }
8369
            } else {
8370
                if ($arrLP[$i]['item_type'] == 'dir') {
8371
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8372
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8373
                }
8374
            }
8375
        }
8376
8377
        $parentSelect = $form->addSelect(
8378
            'parent',
8379
            get_lang('Parent'),
8380
            [],
8381
            [
8382
                'id' => 'idParent',
8383
                'onchange' => 'javascript: load_cbo(this.value);',
8384
            ]
8385
        );
8386
8387
        $my_count = 0;
8388
        foreach ($arrHide as $key => $value) {
8389
            if ($my_count != 0) {
8390
                // The LP name is also the first section and is not in the same charset like the other sections.
8391
                $value['value'] = Security::remove_XSS($value['value']);
8392
                $parentSelect->addOption(
8393
                    $value['value'],
8394
                    $key,
8395
                    'style="padding-left:'.$value['padding'].'px;"'
8396
                );
8397
            } else {
8398
                $value['value'] = Security::remove_XSS($value['value']);
8399
                $parentSelect->addOption(
8400
                    $value['value'],
8401
                    $key,
8402
                    'style="padding-left:'.$value['padding'].'px;"'
8403
                );
8404
            }
8405
            $my_count++;
8406
        }
8407
8408
        if (!empty($id)) {
8409
            $parentSelect->setSelected($parent);
8410
        } else {
8411
            $parent_item_id = Session::read('parent_item_id', 0);
8412
            $parentSelect->setSelected($parent_item_id);
8413
        }
8414
8415
        if (is_array($arrLP)) {
8416
            reset($arrLP);
8417
        }
8418
8419
        $arrHide = [];
8420
        // POSITION
8421
        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...
8422
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8423
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8424
            ) {
8425
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8426
            }
8427
        }
8428
8429
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8430
8431
        $position = $form->addSelect(
8432
            'previous',
8433
            get_lang('Position'),
8434
            [],
8435
            ['id' => 'previous']
8436
        );
8437
8438
        $position->addOption(get_lang('FirstPosition'), 0);
8439
        foreach ($arrHide as $key => $value) {
8440
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8441
            $position->addOption(
8442
                $value['value'],
8443
                $key,
8444
                'style="padding-left:'.$padding.'px;"'
8445
            );
8446
        }
8447
8448
        $position->setSelected($selectedPosition);
8449
8450
        if (is_array($arrLP)) {
8451
            reset($arrLP);
8452
        }
8453
8454
        if ($action != 'move') {
8455
            $arrHide = [];
8456
            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...
8457
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8458
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8459
                ) {
8460
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8461
                }
8462
            }
8463
8464
            if (!$no_display_add) {
8465
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8466
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8467
                if ($extra_info == 'new' || $item_type == TOOL_DOCUMENT ||
8468
                    $item_type == TOOL_LP_FINAL_ITEM || $edit == 'true'
8469
                ) {
8470
                    if (isset($_POST['content'])) {
8471
                        $content = stripslashes($_POST['content']);
8472
                    } elseif (is_array($extra_info)) {
8473
                        //If it's an html document or a text file
8474
                        if (!$no_display_edit_textarea) {
8475
                            $content = $this->display_document(
8476
                                $extra_info['path'],
8477
                                false,
8478
                                false
8479
                            );
8480
                        }
8481
                    } elseif (is_numeric($extra_info)) {
8482
                        $content = $this->display_document(
8483
                            $extra_info,
8484
                            false,
8485
                            false
8486
                        );
8487
                    } else {
8488
                        $content = '';
8489
                    }
8490
8491
                    if (!$no_display_edit_textarea) {
8492
                        // We need to calculate here some specific settings for the online editor.
8493
                        // The calculated settings work for documents in the Documents tool
8494
                        // (on the root or in subfolders).
8495
                        // For documents in native scorm packages it is unclear whether the
8496
                        // online editor should be activated or not.
8497
8498
                        // A new document, it is in the root of the repository.
8499
                        $relative_path = '';
8500
                        $relative_prefix = '';
8501
                        if (is_array($extra_info) && $extra_info != 'new') {
8502
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8503
                            $relative_path = explode('/', $extra_info['dir']);
8504
                            $cnt = count($relative_path) - 2;
8505
                            if ($cnt < 0) {
8506
                                $cnt = 0;
8507
                            }
8508
                            $relative_prefix = str_repeat('../', $cnt);
8509
                            $relative_path = array_slice($relative_path, 1, $cnt);
8510
                            $relative_path = implode('/', $relative_path);
8511
                            if (strlen($relative_path) > 0) {
8512
                                $relative_path = $relative_path.'/';
8513
                            }
8514
                        } else {
8515
                            $result = $this->generate_lp_folder($_course);
8516
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8517
                            $relative_prefix = '../../';
8518
                        }
8519
8520
                        $editor_config = [
8521
                            'ToolbarSet' => 'LearningPathDocuments',
8522
                            'Width' => '100%',
8523
                            'Height' => '500',
8524
                            'FullPage' => true,
8525
                            'CreateDocumentDir' => $relative_prefix,
8526
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8527
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8528
                        ];
8529
8530
                        if ($_GET['action'] == 'add_item') {
8531
                            $class = 'add';
8532
                            $text = get_lang('LPCreateDocument');
8533
                        } else {
8534
                            if ($_GET['action'] == 'edit_item') {
8535
                                $class = 'save';
8536
                                $text = get_lang('SaveDocument');
8537
                            }
8538
                        }
8539
8540
                        $form->addButtonSave($text, 'submit_button');
8541
                        $renderer = $form->defaultRenderer();
8542
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8543
                        $form->addElement('html', '<div class="editor-lp">');
8544
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8545
                        $form->addElement('html', '</div>');
8546
                        $defaults['content_lp'] = $content;
8547
                    }
8548
                } elseif (is_numeric($extra_info)) {
8549
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8550
8551
                    $return = $this->display_document($extra_info, true, true, true);
8552
                    $form->addElement('html', $return);
8553
                }
8554
            }
8555
        }
8556
        if (isset($extra_info['item_type']) &&
8557
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8558
        ) {
8559
            $parentSelect->freeze();
8560
            $position->freeze();
8561
        }
8562
8563
        if ($action == 'move') {
8564
            $form->addElement('hidden', 'title', $item_title);
8565
            $form->addElement('hidden', 'description', $item_description);
8566
        }
8567
        if (is_numeric($extra_info)) {
8568
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8569
            $form->addElement('hidden', 'path', $extra_info);
8570
        } elseif (is_array($extra_info)) {
8571
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8572
            $form->addElement('hidden', 'path', $extra_info['path']);
8573
        }
8574
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8575
        $form->addElement('hidden', 'post_time', time());
8576
        $form->setDefaults($defaults);
8577
8578
        return $form->returnForm();
8579
    }
8580
8581
    /**
8582
     * Returns the form to update or create a read-out text.
8583
     *
8584
     * @param string $action     "add" or "edit"
8585
     * @param int    $id         ID of the lp_item (if already exists)
8586
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8587
     *
8588
     * @throws Exception
8589
     * @throws HTML_QuickForm_Error
8590
     *
8591
     * @return string HTML form
8592
     */
8593
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8594
    {
8595
        $course_id = api_get_course_int_id();
8596
        $_course = api_get_course_info();
8597
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8598
8599
        $no_display_edit_textarea = false;
8600
        $item_description = '';
8601
        //If action==edit document
8602
        //We don't display the document form if it's not an editable document (html or txt file)
8603
        if ($action == 'edit') {
8604
            if (is_array($extra_info)) {
8605
                $path_parts = pathinfo($extra_info['dir']);
8606
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8607
                    $no_display_edit_textarea = true;
8608
                }
8609
            }
8610
        }
8611
        $no_display_add = false;
8612
8613
        $item_title = '';
8614
        $item_description = '';
8615
        if ($id != 0 && is_array($extra_info)) {
8616
            $item_title = stripslashes($extra_info['title']);
8617
            $item_description = stripslashes($extra_info['description']);
8618
            $item_terms = stripslashes($extra_info['terms']);
8619
            if (empty($item_title)) {
8620
                $path_parts = pathinfo($extra_info['path']);
8621
                $item_title = stripslashes($path_parts['filename']);
8622
            }
8623
        } elseif (is_numeric($extra_info)) {
8624
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8625
            $result = Database::query($sql);
8626
            $row = Database::fetch_array($result);
8627
            $item_title = $row['title'];
8628
            $item_title = str_replace('_', ' ', $item_title);
8629
            if (empty($item_title)) {
8630
                $path_parts = pathinfo($row['path']);
8631
                $item_title = stripslashes($path_parts['filename']);
8632
            }
8633
        }
8634
8635
        $parent = 0;
8636
        if ($id != 0 && is_array($extra_info)) {
8637
            $parent = $extra_info['parent_item_id'];
8638
        }
8639
8640
        $arrLP = $this->getItemsForForm();
8641
        $this->tree_array($arrLP);
8642
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8643
        unset($this->arrMenu);
8644
8645
        if ($action === 'add') {
8646
            $formHeader = get_lang('CreateTheDocument');
8647
        } else {
8648
            $formHeader = get_lang('EditTheCurrentDocument');
8649
        }
8650
8651
        if ('edit' === $action) {
8652
            $urlAudioIcon = Display::url(
8653
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
8654
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8655
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8656
            );
8657
        } else {
8658
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
8659
        }
8660
8661
        $form = new FormValidator(
8662
            'frm_add_reading',
8663
            'POST',
8664
            $this->getCurrentBuildingModeURL(),
8665
            '',
8666
            ['enctype' => 'multipart/form-data']
8667
        );
8668
        $form->addHeader($formHeader);
8669
        $form->addHtml(
8670
            Display::return_message(
8671
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
8672
                'normal',
8673
                false
8674
            )
8675
        );
8676
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8677
        $defaults['description'] = $item_description;
8678
8679
        $data = $this->generate_lp_folder($_course);
8680
8681
        if ($action != 'edit') {
8682
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8683
            DocumentManager::build_directory_selector(
8684
                $folders,
8685
                '',
8686
                [],
8687
                true,
8688
                $form,
8689
                'directory_parent_id'
8690
            );
8691
        }
8692
8693
        if (isset($data['id'])) {
8694
            $defaults['directory_parent_id'] = $data['id'];
8695
        }
8696
        $this->setItemTitle($form);
8697
8698
        $arrHide[0]['value'] = $this->name;
8699
        $arrHide[0]['padding'] = 20;
8700
8701
        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...
8702
            if ($action != 'add') {
8703
                if ($arrLP[$i]['item_type'] == 'dir' &&
8704
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8705
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8706
                ) {
8707
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8708
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8709
                }
8710
            } else {
8711
                if ($arrLP[$i]['item_type'] == 'dir') {
8712
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8713
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8714
                }
8715
            }
8716
        }
8717
8718
        $parent_select = $form->addSelect(
8719
            'parent',
8720
            get_lang('Parent'),
8721
            [],
8722
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
8723
        );
8724
8725
        $my_count = 0;
8726
        foreach ($arrHide as $key => $value) {
8727
            if ($my_count != 0) {
8728
                // The LP name is also the first section and is not in the same charset like the other sections.
8729
                $value['value'] = Security::remove_XSS($value['value']);
8730
                $parent_select->addOption(
8731
                    $value['value'],
8732
                    $key,
8733
                    'style="padding-left:'.$value['padding'].'px;"'
8734
                );
8735
            } else {
8736
                $value['value'] = Security::remove_XSS($value['value']);
8737
                $parent_select->addOption(
8738
                    $value['value'],
8739
                    $key,
8740
                    'style="padding-left:'.$value['padding'].'px;"'
8741
                );
8742
            }
8743
            $my_count++;
8744
        }
8745
8746
        if (!empty($id)) {
8747
            $parent_select->setSelected($parent);
8748
        } else {
8749
            $parent_item_id = Session::read('parent_item_id', 0);
8750
            $parent_select->setSelected($parent_item_id);
8751
        }
8752
8753
        if (is_array($arrLP)) {
8754
            reset($arrLP);
8755
        }
8756
8757
        $arrHide = [];
8758
        $s_selected_position = null;
8759
8760
        // POSITION
8761
        $lastPosition = null;
8762
8763
        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...
8764
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
8765
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8766
            ) {
8767
                if ((isset($extra_info['previous_item_id']) &&
8768
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
8769
                ) {
8770
                    $s_selected_position = $arrLP[$i]['id'];
8771
                }
8772
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8773
            }
8774
            $lastPosition = $arrLP[$i]['id'];
8775
        }
8776
8777
        if (empty($s_selected_position)) {
8778
            $s_selected_position = $lastPosition;
8779
        }
8780
8781
        $position = $form->addSelect(
8782
            'previous',
8783
            get_lang('Position'),
8784
            []
8785
        );
8786
        $position->addOption(get_lang('FirstPosition'), 0);
8787
8788
        foreach ($arrHide as $key => $value) {
8789
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8790
            $position->addOption(
8791
                $value['value'],
8792
                $key,
8793
                'style="padding-left:'.$padding.'px;"'
8794
            );
8795
        }
8796
        $position->setSelected($s_selected_position);
8797
8798
        if (is_array($arrLP)) {
8799
            reset($arrLP);
8800
        }
8801
8802
        $arrHide = [];
8803
8804
        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...
8805
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
8806
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8807
            ) {
8808
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8809
            }
8810
        }
8811
8812
        if (!$no_display_add) {
8813
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8814
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8815
8816
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
8817
                if (!$no_display_edit_textarea) {
8818
                    $content = '';
8819
8820
                    if (isset($_POST['content'])) {
8821
                        $content = stripslashes($_POST['content']);
8822
                    } elseif (is_array($extra_info)) {
8823
                        $content = $this->display_document($extra_info['path'], false, false);
8824
                    } elseif (is_numeric($extra_info)) {
8825
                        $content = $this->display_document($extra_info, false, false);
8826
                    }
8827
8828
                    // A new document, it is in the root of the repository.
8829
                    if (is_array($extra_info) && $extra_info != 'new') {
8830
                    } else {
8831
                        $this->generate_lp_folder($_course);
8832
                    }
8833
8834
                    if ($_GET['action'] == 'add_item') {
8835
                        $text = get_lang('LPCreateDocument');
8836
                    } else {
8837
                        $text = get_lang('SaveDocument');
8838
                    }
8839
8840
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
8841
                    $form
8842
                        ->defaultRenderer()
8843
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
8844
                    $form->addButtonSave($text, 'submit_button');
8845
                    $defaults['content_lp'] = $content;
8846
                }
8847
            } elseif (is_numeric($extra_info)) {
8848
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8849
8850
                $return = $this->display_document($extra_info, true, true, true);
8851
                $form->addElement('html', $return);
8852
            }
8853
        }
8854
8855
        if (is_numeric($extra_info)) {
8856
            $form->addElement('hidden', 'path', $extra_info);
8857
        } elseif (is_array($extra_info)) {
8858
            $form->addElement('hidden', 'path', $extra_info['path']);
8859
        }
8860
8861
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
8862
        $form->addElement('hidden', 'post_time', time());
8863
        $form->setDefaults($defaults);
8864
8865
        return $form->returnForm();
8866
    }
8867
8868
    /**
8869
     * @param array  $courseInfo
8870
     * @param string $content
8871
     * @param string $title
8872
     * @param int    $parentId
8873
     *
8874
     * @throws \Doctrine\ORM\ORMException
8875
     * @throws \Doctrine\ORM\OptimisticLockException
8876
     * @throws \Doctrine\ORM\TransactionRequiredException
8877
     *
8878
     * @return int
8879
     */
8880
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
8881
    {
8882
        $creatorId = api_get_user_id();
8883
        $sessionId = api_get_session_id();
8884
8885
        // Generates folder
8886
        $result = $this->generate_lp_folder($courseInfo);
8887
        $dir = $result['dir'];
8888
8889
        if (empty($parentId) || $parentId == '/') {
8890
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
8891
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
8892
8893
            if ($parentId === '/') {
8894
                $dir = '/';
8895
            }
8896
8897
            // Please, do not modify this dirname formatting.
8898
            if (strstr($dir, '..')) {
8899
                $dir = '/';
8900
            }
8901
8902
            if (!empty($dir[0]) && $dir[0] == '.') {
8903
                $dir = substr($dir, 1);
8904
            }
8905
            if (!empty($dir[0]) && $dir[0] != '/') {
8906
                $dir = '/'.$dir;
8907
            }
8908
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
8909
                $dir .= '/';
8910
            }
8911
        } else {
8912
            $parentInfo = DocumentManager::get_document_data_by_id(
8913
                $parentId,
8914
                $courseInfo['code']
8915
            );
8916
            if (!empty($parentInfo)) {
8917
                $dir = $parentInfo['path'].'/';
8918
            }
8919
        }
8920
8921
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8922
8923
        if (!is_dir($filepath)) {
8924
            $dir = '/';
8925
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
8926
        }
8927
8928
        $originalTitle = !empty($title) ? $title : $_POST['title'];
8929
8930
        if (!empty($title)) {
8931
            $title = api_replace_dangerous_char(stripslashes($title));
8932
        } else {
8933
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
8934
        }
8935
8936
        $title = disable_dangerous_file($title);
8937
        $filename = $title;
8938
        $content = !empty($content) ? $content : $_POST['content_lp'];
8939
        $tmpFileName = $filename;
8940
8941
        $i = 0;
8942
        while (file_exists($filepath.$tmpFileName.'.html')) {
8943
            $tmpFileName = $filename.'_'.++$i;
8944
        }
8945
8946
        $filename = $tmpFileName.'.html';
8947
        $content = stripslashes($content);
8948
8949
        if (file_exists($filepath.$filename)) {
8950
            return 0;
8951
        }
8952
8953
        $putContent = file_put_contents($filepath.$filename, $content);
8954
8955
        if ($putContent === false) {
8956
            return 0;
8957
        }
8958
8959
        $fileSize = filesize($filepath.$filename);
8960
        $saveFilePath = $dir.$filename;
8961
8962
        $document = DocumentManager::addDocument(
8963
            $courseInfo,
8964
            $saveFilePath,
8965
            'file',
8966
            $fileSize,
8967
            $tmpFileName,
8968
            '',
8969
            0, //readonly
8970
            true,
8971
            null,
8972
            $sessionId,
8973
            $creatorId
8974
        );
8975
8976
        $documentId = $document->getId();
8977
8978
        if (!$document) {
8979
            return 0;
8980
        }
8981
8982
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
8983
        $newTitle = $originalTitle;
8984
8985
        if ($newComment || $newTitle) {
8986
            $em = Database::getManager();
8987
8988
            if ($newComment) {
8989
                $document->setComment($newComment);
8990
            }
8991
8992
            if ($newTitle) {
8993
                $document->setTitle($newTitle);
8994
            }
8995
8996
            $em->persist($document);
8997
            $em->flush();
8998
        }
8999
9000
        return $documentId;
9001
    }
9002
9003
    /**
9004
     * Return HTML form to add/edit a link item.
9005
     *
9006
     * @param string $action     (add/edit)
9007
     * @param int    $id         Item ID if exists
9008
     * @param mixed  $extra_info
9009
     *
9010
     * @throws Exception
9011
     * @throws HTML_QuickForm_Error
9012
     *
9013
     * @return string HTML form
9014
     */
9015
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9016
    {
9017
        $course_id = api_get_course_int_id();
9018
        $tbl_link = Database::get_course_table(TABLE_LINK);
9019
9020
        $item_title = '';
9021
        $item_description = '';
9022
        $item_url = '';
9023
9024
        if ($id != 0 && is_array($extra_info)) {
9025
            $item_title = stripslashes($extra_info['title']);
9026
            $item_description = stripslashes($extra_info['description']);
9027
            $item_url = stripslashes($extra_info['url']);
9028
        } elseif (is_numeric($extra_info)) {
9029
            $extra_info = (int) $extra_info;
9030
            $sql = "SELECT title, description, url 
9031
                    FROM $tbl_link
9032
                    WHERE c_id = $course_id AND iid = $extra_info";
9033
            $result = Database::query($sql);
9034
            $row = Database::fetch_array($result);
9035
            $item_title = $row['title'];
9036
            $item_description = $row['description'];
9037
            $item_url = $row['url'];
9038
        }
9039
9040
        $form = new FormValidator(
9041
            'edit_link',
9042
            'POST',
9043
            $this->getCurrentBuildingModeURL()
9044
        );
9045
        $defaults = [];
9046
        $parent = 0;
9047
        if ($id != 0 && is_array($extra_info)) {
9048
            $parent = $extra_info['parent_item_id'];
9049
        }
9050
9051
        $arrLP = $this->getItemsForForm();
9052
9053
        $this->tree_array($arrLP);
9054
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9055
        unset($this->arrMenu);
9056
9057
        if ($action == 'add') {
9058
            $legend = get_lang('CreateTheLink');
9059
        } elseif ($action == 'move') {
9060
            $legend = get_lang('MoveCurrentLink');
9061
        } else {
9062
            $legend = get_lang('EditCurrentLink');
9063
        }
9064
9065
        $form->addHeader($legend);
9066
9067
        if ($action != 'move') {
9068
            $this->setItemTitle($form);
9069
            $defaults['title'] = $item_title;
9070
        }
9071
9072
        $selectParent = $form->addSelect(
9073
            'parent',
9074
            get_lang('Parent'),
9075
            [],
9076
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9077
        );
9078
        $selectParent->addOption($this->name, 0);
9079
        $arrHide = [
9080
            $id,
9081
        ];
9082
9083
        $parent_item_id = Session::read('parent_item_id', 0);
9084
9085
        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...
9086
            if ($action != 'add') {
9087
                if (
9088
                    ($arrLP[$i]['item_type'] == 'dir') &&
9089
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9090
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9091
                ) {
9092
                    $selectParent->addOption(
9093
                        $arrLP[$i]['title'],
9094
                        $arrLP[$i]['id'],
9095
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9096
                    );
9097
9098
                    if ($parent == $arrLP[$i]['id']) {
9099
                        $selectParent->setSelected($arrLP[$i]['id']);
9100
                    }
9101
                } else {
9102
                    $arrHide[] = $arrLP[$i]['id'];
9103
                }
9104
            } else {
9105
                if ($arrLP[$i]['item_type'] == 'dir') {
9106
                    $selectParent->addOption(
9107
                        $arrLP[$i]['title'],
9108
                        $arrLP[$i]['id'],
9109
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9110
                    );
9111
9112
                    if ($parent_item_id == $arrLP[$i]['id']) {
9113
                        $selectParent->setSelected($arrLP[$i]['id']);
9114
                    }
9115
                }
9116
            }
9117
        }
9118
9119
        if (is_array($arrLP)) {
9120
            reset($arrLP);
9121
        }
9122
9123
        $selectPrevious = $form->addSelect(
9124
            'previous',
9125
            get_lang('Position'),
9126
            [],
9127
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9128
        );
9129
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9130
9131
        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...
9132
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9133
                $selectPrevious->addOption(
9134
                    $arrLP[$i]['title'],
9135
                    $arrLP[$i]['id']
9136
                );
9137
9138
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9139
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9140
                } elseif ($action == 'add') {
9141
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9142
                }
9143
            }
9144
        }
9145
9146
        if ($action != 'move') {
9147
            $urlAttributes = ['class' => 'learnpath_item_form'];
9148
9149
            if (is_numeric($extra_info)) {
9150
                $urlAttributes['disabled'] = 'disabled';
9151
            }
9152
9153
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9154
            $defaults['url'] = $item_url;
9155
            $arrHide = [];
9156
            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...
9157
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9158
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9159
                }
9160
            }
9161
        }
9162
9163
        if ($action == 'add') {
9164
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9165
        } else {
9166
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9167
        }
9168
9169
        if ($action == 'move') {
9170
            $form->addHidden('title', $item_title);
9171
            $form->addHidden('description', $item_description);
9172
        }
9173
9174
        if (is_numeric($extra_info)) {
9175
            $form->addHidden('path', $extra_info);
9176
        } elseif (is_array($extra_info)) {
9177
            $form->addHidden('path', $extra_info['path']);
9178
        }
9179
        $form->addHidden('type', TOOL_LINK);
9180
        $form->addHidden('post_time', time());
9181
        $form->setDefaults($defaults);
9182
9183
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9184
    }
9185
9186
    /**
9187
     * Return HTML form to add/edit a student publication (work).
9188
     *
9189
     * @param string $action
9190
     * @param int    $id         Item ID if already exists
9191
     * @param string $extra_info
9192
     *
9193
     * @throws Exception
9194
     *
9195
     * @return string HTML form
9196
     */
9197
    public function display_student_publication_form(
9198
        $action = 'add',
9199
        $id = 0,
9200
        $extra_info = ''
9201
    ) {
9202
        $course_id = api_get_course_int_id();
9203
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9204
9205
        $item_title = get_lang('Student_publication');
9206
        if ($id != 0 && is_array($extra_info)) {
9207
            $item_title = stripslashes($extra_info['title']);
9208
            $item_description = stripslashes($extra_info['description']);
9209
        } elseif (is_numeric($extra_info)) {
9210
            $extra_info = (int) $extra_info;
9211
            $sql = "SELECT title, description
9212
                    FROM $tbl_publication
9213
                    WHERE c_id = $course_id AND id = ".$extra_info;
9214
9215
            $result = Database::query($sql);
9216
            $row = Database::fetch_array($result);
9217
            if ($row) {
9218
                $item_title = $row['title'];
9219
            }
9220
        }
9221
9222
        $parent = 0;
9223
        if ($id != 0 && is_array($extra_info)) {
9224
            $parent = $extra_info['parent_item_id'];
9225
        }
9226
9227
        $arrLP = $this->getItemsForForm();
9228
9229
        $this->tree_array($arrLP);
9230
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9231
        unset($this->arrMenu);
9232
9233
        $form = new FormValidator('frm_student_publication', 'post', '#');
9234
9235
        if ($action == 'add') {
9236
            $form->addHeader(get_lang('Student_publication'));
9237
        } elseif ($action == 'move') {
9238
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9239
        } else {
9240
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9241
        }
9242
9243
        if ($action != 'move') {
9244
            $this->setItemTitle($form);
9245
        }
9246
9247
        $parentSelect = $form->addSelect(
9248
            'parent',
9249
            get_lang('Parent'),
9250
            ['0' => $this->name],
9251
            [
9252
                'onchange' => 'javascript: load_cbo(this.value);',
9253
                'class' => 'learnpath_item_form',
9254
                'id' => 'idParent',
9255
            ]
9256
        );
9257
9258
        $arrHide = [$id];
9259
        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...
9260
            if ($action != 'add') {
9261
                if (
9262
                    ($arrLP[$i]['item_type'] == 'dir') &&
9263
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9264
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9265
                ) {
9266
                    $parentSelect->addOption(
9267
                        $arrLP[$i]['title'],
9268
                        $arrLP[$i]['id'],
9269
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9270
                    );
9271
9272
                    if ($parent == $arrLP[$i]['id']) {
9273
                        $parentSelect->setSelected($arrLP[$i]['id']);
9274
                    }
9275
                } else {
9276
                    $arrHide[] = $arrLP[$i]['id'];
9277
                }
9278
            } else {
9279
                if ($arrLP[$i]['item_type'] == 'dir') {
9280
                    $parentSelect->addOption(
9281
                        $arrLP[$i]['title'],
9282
                        $arrLP[$i]['id'],
9283
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9284
                    );
9285
9286
                    if ($parent == $arrLP[$i]['id']) {
9287
                        $parentSelect->setSelected($arrLP[$i]['id']);
9288
                    }
9289
                }
9290
            }
9291
        }
9292
9293
        if (is_array($arrLP)) {
9294
            reset($arrLP);
9295
        }
9296
9297
        $previousSelect = $form->addSelect(
9298
            'previous',
9299
            get_lang('Position'),
9300
            ['0' => get_lang('FirstPosition')],
9301
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9302
        );
9303
9304
        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...
9305
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9306
                $previousSelect->addOption(
9307
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9308
                    $arrLP[$i]['id']
9309
                );
9310
9311
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9312
                    $previousSelect->setSelected($arrLP[$i]['id']);
9313
                } elseif ($action == 'add') {
9314
                    $previousSelect->setSelected($arrLP[$i]['id']);
9315
                }
9316
            }
9317
        }
9318
9319
        if ($action == 'add') {
9320
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9321
        } else {
9322
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9323
        }
9324
9325
        if ($action == 'move') {
9326
            $form->addHidden('title', $item_title);
9327
            $form->addHidden('description', $item_description);
9328
        }
9329
9330
        if (is_numeric($extra_info)) {
9331
            $form->addHidden('path', $extra_info);
9332
        } elseif (is_array($extra_info)) {
9333
            $form->addHidden('path', $extra_info['path']);
9334
        }
9335
9336
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9337
        $form->addHidden('post_time', time());
9338
        $form->setDefaults(['title' => $item_title]);
9339
9340
        $return = '<div class="sectioncomment">';
9341
        $return .= $form->returnForm();
9342
        $return .= '</div>';
9343
9344
        return $return;
9345
    }
9346
9347
    /**
9348
     * Displays the menu for manipulating a step.
9349
     *
9350
     * @param id     $item_id
9351
     * @param string $item_type
9352
     *
9353
     * @return string
9354
     */
9355
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9356
    {
9357
        $_course = api_get_course_info();
9358
        $course_code = api_get_course_id();
9359
        $item_id = (int) $item_id;
9360
9361
        $return = '<div class="actions">';
9362
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9363
        $sql = "SELECT * FROM $tbl_lp_item 
9364
                WHERE iid = ".$item_id;
9365
        $result = Database::query($sql);
9366
        $row = Database::fetch_assoc($result);
9367
9368
        $audio_player = null;
9369
        // We display an audio player if needed.
9370
        if (!empty($row['audio'])) {
9371
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document/audio/'.$row['audio'];
9372
9373
            $audio_player .= '<div class="lp_mediaplayer" id="container">'
9374
                .'<audio src="'.$webAudioPath.'" controls>'
9375
                .'</div><br>';
9376
        }
9377
9378
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9379
9380
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9381
            $return .= Display::url(
9382
                Display::return_icon(
9383
                    'edit.png',
9384
                    get_lang('Edit'),
9385
                    [],
9386
                    ICON_SIZE_SMALL
9387
                ),
9388
                $url.'&action=edit_item&path_item='.$row['path']
9389
            );
9390
9391
            $return .= Display::url(
9392
                Display::return_icon(
9393
                    'move.png',
9394
                    get_lang('Move'),
9395
                    [],
9396
                    ICON_SIZE_SMALL
9397
                ),
9398
                $url.'&action=move_item'
9399
            );
9400
        }
9401
9402
        // Commented for now as prerequisites cannot be added to chapters.
9403
        if ($item_type != 'dir') {
9404
            $return .= Display::url(
9405
                Display::return_icon(
9406
                    'accept.png',
9407
                    get_lang('LearnpathPrerequisites'),
9408
                    [],
9409
                    ICON_SIZE_SMALL
9410
                ),
9411
                $url.'&action=edit_item_prereq'
9412
            );
9413
        }
9414
        $return .= Display::url(
9415
            Display::return_icon(
9416
                'delete.png',
9417
                get_lang('Delete'),
9418
                [],
9419
                ICON_SIZE_SMALL
9420
            ),
9421
            $url.'&action=delete_item'
9422
        );
9423
9424
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9425
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9426
            if (empty($documentData)) {
9427
                // Try with iid
9428
                $table = Database::get_course_table(TABLE_DOCUMENT);
9429
                $sql = "SELECT path FROM $table
9430
                        WHERE 
9431
                              c_id = ".api_get_course_int_id()." AND 
9432
                              iid = ".$row['path']." AND 
9433
                              path NOT LIKE '%_DELETED_%'";
9434
                $result = Database::query($sql);
9435
                $documentData = Database::fetch_array($result);
9436
                if ($documentData) {
9437
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9438
                }
9439
            }
9440
            if (isset($documentData['absolute_path_from_document'])) {
9441
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9442
            }
9443
        }
9444
9445
        $return .= '</div>';
9446
9447
        if (!empty($audio_player)) {
9448
            $return .= $audio_player;
9449
        }
9450
9451
        return $return;
9452
    }
9453
9454
    /**
9455
     * Creates the javascript needed for filling up the checkboxes without page reload.
9456
     *
9457
     * @return string
9458
     */
9459
    public function get_js_dropdown_array()
9460
    {
9461
        $course_id = api_get_course_int_id();
9462
        $return = 'var child_name = new Array();'."\n";
9463
        $return .= 'var child_value = new Array();'."\n\n";
9464
        $return .= 'child_name[0] = new Array();'."\n";
9465
        $return .= 'child_value[0] = new Array();'."\n\n";
9466
9467
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9468
        $sql = "SELECT * FROM ".$tbl_lp_item."
9469
                WHERE 
9470
                    c_id = $course_id AND 
9471
                    lp_id = ".$this->lp_id." AND 
9472
                    parent_item_id = 0
9473
                ORDER BY display_order ASC";
9474
        $res_zero = Database::query($sql);
9475
        $i = 0;
9476
9477
        $list = $this->getItemsForForm(true);
9478
9479
        foreach ($list as $row_zero) {
9480
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9481
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9482
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9483
                }
9484
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9485
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9486
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9487
            }
9488
        }
9489
9490
        $return .= "\n";
9491
        $sql = "SELECT * FROM $tbl_lp_item
9492
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9493
        $res = Database::query($sql);
9494
        while ($row = Database::fetch_array($res)) {
9495
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9496
                           WHERE
9497
                                c_id = ".$course_id." AND
9498
                                parent_item_id = ".$row['iid']."
9499
                           ORDER BY display_order ASC";
9500
            $res_parent = Database::query($sql_parent);
9501
            $i = 0;
9502
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9503
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9504
9505
            while ($row_parent = Database::fetch_array($res_parent)) {
9506
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9507
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9508
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9509
            }
9510
            $return .= "\n";
9511
        }
9512
9513
        $return .= "
9514
            function load_cbo(id) {
9515
                if (!id) {
9516
                    return false;
9517
                }
9518
            
9519
                var cbo = document.getElementById('previous');
9520
                for(var i = cbo.length - 1; i > 0; i--) {
9521
                    cbo.options[i] = null;
9522
                }
9523
            
9524
                var k=0;
9525
                for(var i = 1; i <= child_name[id].length; i++){
9526
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9527
                    option.style.paddingLeft = '40px';
9528
                    cbo.options[i] = option;
9529
                    k = i;
9530
                }
9531
            
9532
                cbo.options[k].selected = true;
9533
                $('#previous').selectpicker('refresh');
9534
            }";
9535
9536
        return $return;
9537
    }
9538
9539
    /**
9540
     * Display the form to allow moving an item.
9541
     *
9542
     * @param int $item_id Item ID
9543
     *
9544
     * @throws Exception
9545
     * @throws HTML_QuickForm_Error
9546
     *
9547
     * @return string HTML form
9548
     */
9549
    public function display_move_item($item_id)
9550
    {
9551
        $return = '';
9552
        if (is_numeric($item_id)) {
9553
            $item_id = (int) $item_id;
9554
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9555
9556
            $sql = "SELECT * FROM $tbl_lp_item
9557
                    WHERE iid = $item_id";
9558
            $res = Database::query($sql);
9559
            $row = Database::fetch_array($res);
9560
9561
            switch ($row['item_type']) {
9562
                case 'dir':
9563
                case 'asset':
9564
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9565
                    $return .= $this->display_item_form(
9566
                        $row['item_type'],
9567
                        get_lang('MoveCurrentChapter'),
9568
                        'move',
9569
                        $item_id,
9570
                        $row
9571
                    );
9572
                    break;
9573
                case TOOL_DOCUMENT:
9574
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9575
                    $return .= $this->display_document_form('move', $item_id, $row);
9576
                    break;
9577
                case TOOL_LINK:
9578
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9579
                    $return .= $this->display_link_form('move', $item_id, $row);
9580
                    break;
9581
                case TOOL_HOTPOTATOES:
9582
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9583
                    $return .= $this->display_link_form('move', $item_id, $row);
9584
                    break;
9585
                case TOOL_QUIZ:
9586
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9587
                    $return .= $this->display_quiz_form('move', $item_id, $row);
9588
                    break;
9589
                case TOOL_STUDENTPUBLICATION:
9590
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9591
                    $return .= $this->display_student_publication_form('move', $item_id, $row);
9592
                    break;
9593
                case TOOL_FORUM:
9594
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9595
                    $return .= $this->display_forum_form('move', $item_id, $row);
9596
                    break;
9597
                case TOOL_THREAD:
9598
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
9599
                    $return .= $this->display_forum_form('move', $item_id, $row);
9600
                    break;
9601
            }
9602
        }
9603
9604
        return $return;
9605
    }
9606
9607
    /**
9608
     * Return HTML form to allow prerequisites selection.
9609
     *
9610
     * @todo use FormValidator
9611
     *
9612
     * @param int Item ID
9613
     *
9614
     * @return string HTML form
9615
     */
9616
    public function display_item_prerequisites_form($item_id = 0)
9617
    {
9618
        $course_id = api_get_course_int_id();
9619
        $item_id = (int) $item_id;
9620
9621
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9622
9623
        /* Current prerequisite */
9624
        $sql = "SELECT * FROM $tbl_lp_item
9625
                WHERE iid = $item_id";
9626
        $result = Database::query($sql);
9627
        $row = Database::fetch_array($result);
9628
        $prerequisiteId = $row['prerequisite'];
9629
        $return = '<legend>';
9630
        $return .= get_lang('AddEditPrerequisites');
9631
        $return .= '</legend>';
9632
        $return .= '<form method="POST">';
9633
        $return .= '<div class="table-responsive">';
9634
        $return .= '<table class="table table-hover">';
9635
        $return .= '<thead>';
9636
        $return .= '<tr>';
9637
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9638
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9639
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9640
        $return .= '</tr>';
9641
        $return .= '</thead>';
9642
9643
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9644
        $return .= '<tbody>';
9645
        $return .= '<tr>';
9646
        $return .= '<td colspan="3">';
9647
        $return .= '<div class="radio learnpath"><label for="idNone">';
9648
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9649
        $return .= get_lang('None').'</label>';
9650
        $return .= '</div>';
9651
        $return .= '</tr>';
9652
9653
        $sql = "SELECT * FROM $tbl_lp_item
9654
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9655
        $result = Database::query($sql);
9656
9657
        $selectedMinScore = [];
9658
        $selectedMaxScore = [];
9659
        $masteryScore = [];
9660
        while ($row = Database::fetch_array($result)) {
9661
            if ($row['iid'] == $item_id) {
9662
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9663
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9664
            }
9665
            $masteryScore[$row['iid']] = $row['mastery_score'];
9666
        }
9667
9668
        $arrLP = $this->getItemsForForm();
9669
        $this->tree_array($arrLP);
9670
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9671
        unset($this->arrMenu);
9672
9673
        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...
9674
            $item = $arrLP[$i];
9675
9676
            if ($item['id'] == $item_id) {
9677
                break;
9678
            }
9679
9680
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9681
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
9682
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
9683
9684
            $return .= '<tr>';
9685
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
9686
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
9687
            $return .= '<label for="id'.$item['id'].'">';
9688
            $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'].'" />';
9689
9690
            $icon_name = str_replace(' ', '', $item['item_type']);
9691
9692
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
9693
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
9694
            } else {
9695
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
9696
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
9697
                } else {
9698
                    $return .= Display::return_icon('folder_document.png');
9699
                }
9700
            }
9701
9702
            $return .= $item['title'].'</label>';
9703
            $return .= '</div>';
9704
            $return .= '</td>';
9705
9706
            if ($item['item_type'] == TOOL_QUIZ) {
9707
                // lets update max_score Quiz information depending of the Quiz Advanced properties
9708
                $lpItemObj = new LpItem($course_id, $item['id']);
9709
                $exercise = new Exercise($course_id);
9710
                $exercise->read($lpItemObj->path);
9711
                $lpItemObj->max_score = $exercise->get_max_score();
9712
                $lpItemObj->update();
9713
                $item['max_score'] = $lpItemObj->max_score;
9714
9715
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
9716
                    // Backwards compatibility with 1.9.x use mastery_score as min value
9717
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
9718
                }
9719
9720
                $return .= '<td>';
9721
                $return .= '<input 
9722
                    class="form-control" 
9723
                    size="4" maxlength="3" 
9724
                    name="min_'.$item['id'].'" 
9725
                    type="number" 
9726
                    min="0" 
9727
                    step="1" 
9728
                    max="'.$item['max_score'].'" 
9729
                    value="'.$selectedMinScoreValue.'" 
9730
                />';
9731
                $return .= '</td>';
9732
                $return .= '<td>';
9733
                $return .= '<input 
9734
                    class="form-control" 
9735
                    size="4" 
9736
                    maxlength="3" 
9737
                    name="max_'.$item['id'].'" 
9738
                    type="number" 
9739
                    min="0" 
9740
                    step="1" 
9741
                    max="'.$item['max_score'].'" 
9742
                    value="'.$selectedMaxScoreValue.'" 
9743
                />';
9744
                $return .= '</td>';
9745
            }
9746
9747
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
9748
                $return .= '<td>';
9749
                $return .= '<input 
9750
                    size="4" 
9751
                    maxlength="3" 
9752
                    name="min_'.$item['id'].'" 
9753
                    type="number" 
9754
                    min="0" 
9755
                    step="1" 
9756
                    max="'.$item['max_score'].'" 
9757
                    value="'.$selectedMinScoreValue.'" 
9758
                />';
9759
                $return .= '</td>';
9760
                $return .= '<td>';
9761
                $return .= '<input 
9762
                    size="4" 
9763
                    maxlength="3" 
9764
                    name="max_'.$item['id'].'" 
9765
                    type="number" 
9766
                    min="0" 
9767
                    step="1" 
9768
                    max="'.$item['max_score'].'" 
9769
                    value="'.$selectedMaxScoreValue.'" 
9770
                />';
9771
                $return .= '</td>';
9772
            }
9773
            $return .= '</tr>';
9774
        }
9775
        $return .= '<tr>';
9776
        $return .= '</tr>';
9777
        $return .= '</tbody>';
9778
        $return .= '</table>';
9779
        $return .= '</div>';
9780
        $return .= '<div class="form-group">';
9781
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
9782
            get_lang('ModifyPrerequisites').'</button>';
9783
        $return .= '</form>';
9784
9785
        return $return;
9786
    }
9787
9788
    /**
9789
     * Return HTML list to allow prerequisites selection for lp.
9790
     *
9791
     * @return string HTML form
9792
     */
9793
    public function display_lp_prerequisites_list()
9794
    {
9795
        $course_id = api_get_course_int_id();
9796
        $lp_id = $this->lp_id;
9797
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
9798
9799
        // get current prerequisite
9800
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
9801
        $result = Database::query($sql);
9802
        $row = Database::fetch_array($result);
9803
        $prerequisiteId = $row['prerequisite'];
9804
        $session_id = api_get_session_id();
9805
        $session_condition = api_get_session_condition($session_id, true, true);
9806
        $sql = "SELECT * FROM $tbl_lp
9807
                WHERE c_id = $course_id $session_condition
9808
                ORDER BY display_order ";
9809
        $rs = Database::query($sql);
9810
        $return = '';
9811
        $return .= '<select name="prerequisites" class="form-control">';
9812
        $return .= '<option value="0">'.get_lang('None').'</option>';
9813
        if (Database::num_rows($rs) > 0) {
9814
            while ($row = Database::fetch_array($rs)) {
9815
                if ($row['id'] == $lp_id) {
9816
                    continue;
9817
                }
9818
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
9819
            }
9820
        }
9821
        $return .= '</select>';
9822
9823
        return $return;
9824
    }
9825
9826
    /**
9827
     * Creates a list with all the documents in it.
9828
     *
9829
     * @param bool $showInvisibleFiles
9830
     *
9831
     * @throws Exception
9832
     * @throws HTML_QuickForm_Error
9833
     *
9834
     * @return string
9835
     */
9836
    public function get_documents($showInvisibleFiles = false)
9837
    {
9838
        $course_info = api_get_course_info();
9839
        $sessionId = api_get_session_id();
9840
        $documentTree = DocumentManager::get_document_preview(
9841
            $course_info,
9842
            $this->lp_id,
9843
            null,
9844
            $sessionId,
9845
            true,
9846
            null,
9847
            null,
9848
            $showInvisibleFiles,
9849
            true
9850
        );
9851
9852
        $headers = [
9853
            get_lang('Files'),
9854
            get_lang('CreateTheDocument'),
9855
            get_lang('CreateReadOutText'),
9856
            get_lang('Upload'),
9857
        ];
9858
9859
        $form = new FormValidator(
9860
            'form_upload',
9861
            'POST',
9862
            $this->getCurrentBuildingModeURL(),
9863
            '',
9864
            ['enctype' => 'multipart/form-data']
9865
        );
9866
9867
        $folders = DocumentManager::get_all_document_folders(
9868
            api_get_course_info(),
9869
            0,
9870
            true
9871
        );
9872
9873
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
9874
9875
        DocumentManager::build_directory_selector(
9876
            $folders,
9877
            $lpPathInfo['id'],
9878
            [],
9879
            true,
9880
            $form,
9881
            'directory_parent_id'
9882
        );
9883
9884
        $group = [
9885
            $form->createElement(
9886
                'radio',
9887
                'if_exists',
9888
                get_lang('UplWhatIfFileExists'),
9889
                get_lang('UplDoNothing'),
9890
                'nothing'
9891
            ),
9892
            $form->createElement(
9893
                'radio',
9894
                'if_exists',
9895
                null,
9896
                get_lang('UplOverwriteLong'),
9897
                'overwrite'
9898
            ),
9899
            $form->createElement(
9900
                'radio',
9901
                'if_exists',
9902
                null,
9903
                get_lang('UplRenameLong'),
9904
                'rename'
9905
            ),
9906
        ];
9907
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
9908
9909
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
9910
        $defaultFileExistsOption = 'rename';
9911
        if (!empty($fileExistsOption)) {
9912
            $defaultFileExistsOption = $fileExistsOption;
9913
        }
9914
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
9915
9916
        // Check box options
9917
        $form->addElement(
9918
            'checkbox',
9919
            'unzip',
9920
            get_lang('Options'),
9921
            get_lang('Uncompress')
9922
        );
9923
9924
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
9925
        $form->addMultipleUpload($url);
9926
        $new = $this->display_document_form('add', 0);
9927
        $frmReadOutText = $this->displayFrmReadOutText('add');
9928
        $tabs = Display::tabs(
9929
            $headers,
9930
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
9931
            'subtab'
9932
        );
9933
9934
        return $tabs;
9935
    }
9936
9937
    /**
9938
     * Creates a list with all the exercises (quiz) in it.
9939
     *
9940
     * @return string
9941
     */
9942
    public function get_exercises()
9943
    {
9944
        $course_id = api_get_course_int_id();
9945
        $session_id = api_get_session_id();
9946
        $userInfo = api_get_user_info();
9947
9948
        // New for hotpotatoes.
9949
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
9950
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9951
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
9952
        $condition_session = api_get_session_condition($session_id, true, true);
9953
        $setting = api_get_setting('lp.show_invisible_exercise_in_lp_toc') === 'true';
9954
9955
        $activeCondition = ' active <> -1 ';
9956
        if ($setting) {
9957
            $activeCondition = ' active = 1 ';
9958
        }
9959
9960
        $categoryCondition = '';
9961
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
9962
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
9963
            $categoryCondition = " AND exercise_category_id = $categoryId ";
9964
        }
9965
9966
        $keywordCondition = '';
9967
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
9968
9969
        if (!empty($keyword)) {
9970
            $keyword = Database::escape_string($keyword);
9971
            $keywordCondition = " AND title LIKE '%$keyword%' ";
9972
        }
9973
9974
        $sql_quiz = "SELECT * FROM $tbl_quiz
9975
                     WHERE 
9976
                            c_id = $course_id AND 
9977
                            $activeCondition
9978
                            $condition_session 
9979
                            $categoryCondition
9980
                            $keywordCondition
9981
                     ORDER BY title ASC";
9982
9983
        $sql_hot = "SELECT * FROM $tbl_doc
9984
                     WHERE 
9985
                        c_id = $course_id AND 
9986
                        path LIKE '".$uploadPath."/%/%htm%'  
9987
                        $condition_session
9988
                     ORDER BY id ASC";
9989
9990
        $res_quiz = Database::query($sql_quiz);
9991
        $res_hot = Database::query($sql_hot);
9992
9993
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
9994
9995
        // Create a search-box
9996
        $form = new FormValidator('search_simple', 'get', $currentUrl);
9997
        $form->addHidden('action', 'add_item');
9998
        $form->addHidden('type', 'step');
9999
        $form->addHidden('lp_id', $this->lp_id);
10000
        $form->addHidden('lp_build_selected', '2');
10001
10002
        $form->addCourseHiddenParams();
10003
        $form->addText(
10004
            'keyword',
10005
            get_lang('Search'),
10006
            false,
10007
            [
10008
                'aria-label' => get_lang('Search'),
10009
            ]
10010
        );
10011
10012
        if (api_get_configuration_value('allow_exercise_categories')) {
10013
            $manager = new ExerciseCategoryManager();
10014
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10015
            if (!empty($options)) {
10016
                $form->addSelect(
10017
                    'category_id',
10018
                    get_lang('Category'),
10019
                    $options,
10020
                    ['placeholder' => get_lang('SelectAnOption')]
10021
                );
10022
            }
10023
        }
10024
10025
        $form->addButtonSearch(get_lang('Search'));
10026
        $return = $form->returnForm();
10027
10028
        $return .= '<ul class="lp_resource">';
10029
10030
        $return .= '<li class="lp_resource_element">';
10031
        $return .= Display::return_icon('new_exercice.png');
10032
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10033
            get_lang('NewExercise').'</a>';
10034
        $return .= '</li>';
10035
10036
        $previewIcon = Display::return_icon(
10037
            'preview_view.png',
10038
            get_lang('Preview')
10039
        );
10040
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10041
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10042
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10043
10044
        // Display hotpotatoes
10045
        while ($row_hot = Database::fetch_array($res_hot)) {
10046
            $link = Display::url(
10047
                $previewIcon,
10048
                $exerciseUrl.'&file='.$row_hot['path'],
10049
                ['target' => '_blank']
10050
            );
10051
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10052
            $return .= '<a class="moved" href="#">';
10053
            $return .= Display::return_icon(
10054
                'move_everywhere.png',
10055
                get_lang('Move'),
10056
                [],
10057
                ICON_SIZE_TINY
10058
            );
10059
            $return .= '</a> ';
10060
            $return .= Display::return_icon('hotpotatoes_s.png');
10061
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10062
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10063
            $return .= '</li>';
10064
        }
10065
10066
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10067
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10068
            $title = strip_tags(
10069
                api_html_entity_decode($row_quiz['title'])
10070
            );
10071
10072
            $visibility = api_get_item_visibility(
10073
                ['real_id' => $course_id],
10074
                TOOL_QUIZ,
10075
                $row_quiz['iid'],
10076
                $session_id
10077
            );
10078
10079
            $link = Display::url(
10080
                $previewIcon,
10081
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10082
                ['target' => '_blank']
10083
            );
10084
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10085
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10086
            $return .= $quizIcon;
10087
            $sessionStar = api_get_session_image(
10088
                $row_quiz['session_id'],
10089
                $userInfo['status']
10090
            );
10091
            $return .= Display::url(
10092
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10093
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10094
                [
10095
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10096
                ]
10097
            );
10098
            $return .= '</li>';
10099
        }
10100
10101
        $return .= '</ul>';
10102
10103
        return $return;
10104
    }
10105
10106
    /**
10107
     * Creates a list with all the links in it.
10108
     *
10109
     * @return string
10110
     */
10111
    public function get_links()
10112
    {
10113
        $selfUrl = api_get_self();
10114
        $courseIdReq = api_get_cidreq();
10115
        $course = api_get_course_info();
10116
        $userInfo = api_get_user_info();
10117
10118
        $course_id = $course['real_id'];
10119
        $tbl_link = Database::get_course_table(TABLE_LINK);
10120
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10121
        $moveEverywhereIcon = Display::return_icon(
10122
            'move_everywhere.png',
10123
            get_lang('Move'),
10124
            [],
10125
            ICON_SIZE_TINY
10126
        );
10127
10128
        $session_id = api_get_session_id();
10129
        $condition_session = api_get_session_condition(
10130
            $session_id,
10131
            true,
10132
            true,
10133
            'link.session_id'
10134
        );
10135
10136
        $sql = "SELECT 
10137
                    link.id as link_id,
10138
                    link.title as link_title,
10139
                    link.session_id as link_session_id,
10140
                    link.category_id as category_id,
10141
                    link_category.category_title as category_title
10142
                FROM $tbl_link as link
10143
                LEFT JOIN $linkCategoryTable as link_category
10144
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10145
                WHERE link.c_id = $course_id $condition_session
10146
                ORDER BY link_category.category_title ASC, link.title ASC";
10147
        $result = Database::query($sql);
10148
        $categorizedLinks = [];
10149
        $categories = [];
10150
10151
        while ($link = Database::fetch_array($result)) {
10152
            if (!$link['category_id']) {
10153
                $link['category_title'] = get_lang('Uncategorized');
10154
            }
10155
            $categories[$link['category_id']] = $link['category_title'];
10156
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10157
        }
10158
10159
        $linksHtmlCode =
10160
            '<script>
10161
            function toggle_tool(tool, id) {
10162
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10163
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10164
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10165
                } else {
10166
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10167
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10168
                }
10169
            }
10170
        </script>
10171
10172
        <ul class="lp_resource">
10173
            <li class="lp_resource_element">
10174
                '.Display::return_icon('linksnew.gif').'
10175
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10176
                get_lang('LinkAdd').'
10177
                </a>
10178
            </li>';
10179
10180
        foreach ($categorizedLinks as $categoryId => $links) {
10181
            $linkNodes = null;
10182
            foreach ($links as $key => $linkInfo) {
10183
                $title = $linkInfo['link_title'];
10184
                $linkSessionId = $linkInfo['link_session_id'];
10185
10186
                $link = Display::url(
10187
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10188
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10189
                    ['target' => '_blank']
10190
                );
10191
10192
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10193
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10194
                    $linkNodes .=
10195
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10196
                        <a class="moved" href="#">'.
10197
                            $moveEverywhereIcon.
10198
                        '</a>
10199
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10200
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10201
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10202
                        Security::remove_XSS($title).$sessionStar.$link.
10203
                        '</a>
10204
                    </li>';
10205
                }
10206
            }
10207
            $linksHtmlCode .=
10208
                '<li>
10209
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10210
                    <img src="'.Display::returnIconPath('add.png').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10211
                    align="absbottom" />
10212
                </a>
10213
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10214
            </li>
10215
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10216
        }
10217
        $linksHtmlCode .= '</ul>';
10218
10219
        return $linksHtmlCode;
10220
    }
10221
10222
    /**
10223
     * Creates a list with all the student publications in it.
10224
     *
10225
     * @return string
10226
     */
10227
    public function get_student_publications()
10228
    {
10229
        $return = '<ul class="lp_resource">';
10230
        $return .= '<li class="lp_resource_element">';
10231
        $return .= Display::return_icon('works_new.gif');
10232
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10233
            get_lang('AddAssignmentPage').'</a>';
10234
        $return .= '</li>';
10235
10236
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10237
        $works = getWorkListTeacher(0, 100, null, null, null);
10238
        if (!empty($works)) {
10239
            foreach ($works as $work) {
10240
                $link = Display::url(
10241
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10242
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10243
                    ['target' => '_blank']
10244
                );
10245
10246
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10247
                $return .= '<a class="moved" href="#">';
10248
                $return .= Display::return_icon(
10249
                    'move_everywhere.png',
10250
                    get_lang('Move'),
10251
                    [],
10252
                    ICON_SIZE_TINY
10253
                );
10254
                $return .= '</a> ';
10255
10256
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10257
                $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.'">'.
10258
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10259
                </a>';
10260
10261
                $return .= '</li>';
10262
            }
10263
        }
10264
10265
        $return .= '</ul>';
10266
10267
        return $return;
10268
    }
10269
10270
    /**
10271
     * Creates a list with all the forums in it.
10272
     *
10273
     * @return string
10274
     */
10275
    public function get_forums()
10276
    {
10277
        require_once '../forum/forumfunction.inc.php';
10278
10279
        $forumCategories = get_forum_categories();
10280
        $forumsInNoCategory = get_forums_in_category(0);
10281
        if (!empty($forumsInNoCategory)) {
10282
            $forumCategories = array_merge(
10283
                $forumCategories,
10284
                [
10285
                    [
10286
                        'cat_id' => 0,
10287
                        'session_id' => 0,
10288
                        'visibility' => 1,
10289
                        'cat_comment' => null,
10290
                    ],
10291
                ]
10292
            );
10293
        }
10294
10295
        $forumList = get_forums();
10296
        $a_forums = [];
10297
        foreach ($forumCategories as $forumCategory) {
10298
            // The forums in this category.
10299
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10300
            if (!empty($forumsInCategory)) {
10301
                foreach ($forumList as $forum) {
10302
                    if (isset($forum['forum_category']) &&
10303
                        $forum['forum_category'] == $forumCategory['cat_id']
10304
                    ) {
10305
                        $a_forums[] = $forum;
10306
                    }
10307
                }
10308
            }
10309
        }
10310
10311
        $return = '<ul class="lp_resource">';
10312
10313
        // First add link
10314
        $return .= '<li class="lp_resource_element">';
10315
        $return .= Display::return_icon('new_forum.png');
10316
        $return .= Display::url(
10317
            get_lang('CreateANewForum'),
10318
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10319
                'action' => 'add',
10320
                'content' => 'forum',
10321
                'lp_id' => $this->lp_id,
10322
            ]),
10323
            ['title' => get_lang('CreateANewForum')]
10324
        );
10325
        $return .= '</li>';
10326
10327
        $return .= '<script>
10328
            function toggle_forum(forum_id) {
10329
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10330
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10331
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10332
                } else {
10333
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10334
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.png').'";
10335
                }
10336
            }
10337
        </script>';
10338
10339
        foreach ($a_forums as $forum) {
10340
            if (!empty($forum['forum_id'])) {
10341
                $link = Display::url(
10342
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10343
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10344
                    ['target' => '_blank']
10345
                );
10346
10347
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10348
                $return .= '<a class="moved" href="#">';
10349
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10350
                $return .= ' </a>';
10351
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10352
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10353
                                <img src="'.Display::returnIconPath('add.png').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10354
                            </a>
10355
                            <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">'.
10356
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10357
10358
                $return .= '</li>';
10359
10360
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10361
                $a_threads = get_threads($forum['forum_id']);
10362
                if (is_array($a_threads)) {
10363
                    foreach ($a_threads as $thread) {
10364
                        $link = Display::url(
10365
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10366
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10367
                            ['target' => '_blank']
10368
                        );
10369
10370
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10371
                        $return .= '&nbsp;<a class="moved" href="#">';
10372
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10373
                        $return .= ' </a>';
10374
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10375
                        $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.'">'.
10376
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10377
                        $return .= '</li>';
10378
                    }
10379
                }
10380
                $return .= '</div>';
10381
            }
10382
        }
10383
        $return .= '</ul>';
10384
10385
        return $return;
10386
    }
10387
10388
    /**
10389
     * // TODO: The output encoding should be equal to the system encoding.
10390
     *
10391
     * Exports the learning path as a SCORM package. This is the main function that
10392
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10393
     * whole thing and returns the zip.
10394
     *
10395
     * This method needs to be called in PHP5, as it will fail with non-adequate
10396
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10397
     * you need to call it on a learnpath object.
10398
     *
10399
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10400
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10401
     * path has been modified, it should use the generic method here below.
10402
     *
10403
     * @return string Returns the zip package string, or null if error
10404
     */
10405
    public function scormExport()
10406
    {
10407
        api_set_more_memory_and_time_limits();
10408
10409
        $_course = api_get_course_info();
10410
        $course_id = $_course['real_id'];
10411
        // Create the zip handler (this will remain available throughout the method).
10412
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10413
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10414
        $temp_dir_short = uniqid('scorm_export', true);
10415
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10416
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10417
        $zip_folder = new PclZip($temp_zip_file);
10418
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10419
        $root_path = $main_path = api_get_path(SYS_PATH);
10420
        $files_cleanup = [];
10421
10422
        // Place to temporarily stash the zip file.
10423
        // create the temp dir if it doesn't exist
10424
        // or do a cleanup before creating the zip file.
10425
        if (!is_dir($temp_zip_dir)) {
10426
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10427
        } else {
10428
            // Cleanup: Check the temp dir for old files and delete them.
10429
            $handle = opendir($temp_zip_dir);
10430
            while (false !== ($file = readdir($handle))) {
10431
                if ($file != '.' && $file != '..') {
10432
                    unlink("$temp_zip_dir/$file");
10433
                }
10434
            }
10435
            closedir($handle);
10436
        }
10437
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10438
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10439
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10440
        ) {
10441
            // Remove the possible . at the end of the path.
10442
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10443
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10444
            mkdir(
10445
                $dest_path_to_scorm_folder,
10446
                api_get_permissions_for_new_directories(),
10447
                true
10448
            );
10449
            copyr(
10450
                $current_course_path.'/scorm/'.$this->path,
10451
                $dest_path_to_scorm_folder,
10452
                ['imsmanifest'],
10453
                $zip_files
10454
            );
10455
        }
10456
10457
        // Build a dummy imsmanifest structure.
10458
        // Do not add to the zip yet (we still need it).
10459
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10460
        // Aggregation Model official document, section "2.3 Content Packaging".
10461
        // We are going to build a UTF-8 encoded manifest.
10462
        // Later we will recode it to the desired (and supported) encoding.
10463
        $xmldoc = new DOMDocument('1.0');
10464
        $root = $xmldoc->createElement('manifest');
10465
        $root->setAttribute('identifier', 'SingleCourseManifest');
10466
        $root->setAttribute('version', '1.1');
10467
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10468
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10469
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10470
        $root->setAttribute(
10471
            'xsi:schemaLocation',
10472
            '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'
10473
        );
10474
        // Build mandatory sub-root container elements.
10475
        $metadata = $xmldoc->createElement('metadata');
10476
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10477
        $metadata->appendChild($md_schema);
10478
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10479
        $metadata->appendChild($md_schemaversion);
10480
        $root->appendChild($metadata);
10481
10482
        $organizations = $xmldoc->createElement('organizations');
10483
        $resources = $xmldoc->createElement('resources');
10484
10485
        // Build the only organization we will use in building our learnpaths.
10486
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10487
        $organization = $xmldoc->createElement('organization');
10488
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10489
        // To set the title of the SCORM entity (=organization), we take the name given
10490
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10491
        // learning path charset) as it is the encoding that defines how it is stored
10492
        // in the database. Then we convert it to HTML entities again as the "&" character
10493
        // alone is not authorized in XML (must be &amp;).
10494
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10495
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10496
        $organization->appendChild($org_title);
10497
        $folder_name = 'document';
10498
10499
        // Removes the learning_path/scorm_folder path when exporting see #4841
10500
        $path_to_remove = '';
10501
        $path_to_replace = '';
10502
        $result = $this->generate_lp_folder($_course);
10503
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10504
            $path_to_remove = 'document'.$result['dir'];
10505
            $path_to_replace = $folder_name.'/';
10506
        }
10507
10508
        // Fixes chamilo scorm exports
10509
        if ($this->ref === 'chamilo_scorm_export') {
10510
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10511
        }
10512
10513
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10514
        $link_updates = [];
10515
        $links_to_create = [];
10516
        foreach ($this->ordered_items as $index => $itemId) {
10517
            /** @var learnpathItem $item */
10518
            $item = $this->items[$itemId];
10519
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10520
                // Get included documents from this item.
10521
                if ($item->type === 'sco') {
10522
                    $inc_docs = $item->get_resources_from_source(
10523
                        null,
10524
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10525
                    );
10526
                } else {
10527
                    $inc_docs = $item->get_resources_from_source();
10528
                }
10529
10530
                // Give a child element <item> to the <organization> element.
10531
                $my_item_id = $item->get_id();
10532
                $my_item = $xmldoc->createElement('item');
10533
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10534
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10535
                $my_item->setAttribute('isvisible', 'true');
10536
                // Give a child element <title> to the <item> element.
10537
                $my_title = $xmldoc->createElement(
10538
                    'title',
10539
                    htmlspecialchars(
10540
                        api_utf8_encode($item->get_title()),
10541
                        ENT_QUOTES,
10542
                        'UTF-8'
10543
                    )
10544
                );
10545
                $my_item->appendChild($my_title);
10546
                // Give a child element <adlcp:prerequisites> to the <item> element.
10547
                $my_prereqs = $xmldoc->createElement(
10548
                    'adlcp:prerequisites',
10549
                    $this->get_scorm_prereq_string($my_item_id)
10550
                );
10551
                $my_prereqs->setAttribute('type', 'aicc_script');
10552
                $my_item->appendChild($my_prereqs);
10553
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10554
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10555
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10556
                //$xmldoc->createElement('adlcp:timelimitaction','');
10557
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10558
                //$xmldoc->createElement('adlcp:datafromlms','');
10559
                // Give a child element <adlcp:masteryscore> to the <item> element.
10560
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10561
                $my_item->appendChild($my_masteryscore);
10562
10563
                // Attach this item to the organization element or hits parent if there is one.
10564
                if (!empty($item->parent) && $item->parent != 0) {
10565
                    $children = $organization->childNodes;
10566
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10567
                    if (is_object($possible_parent)) {
10568
                        $possible_parent->appendChild($my_item);
10569
                    } else {
10570
                        if ($this->debug > 0) {
10571
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10572
                        }
10573
                    }
10574
                } else {
10575
                    if ($this->debug > 0) {
10576
                        error_log('No parent');
10577
                    }
10578
                    $organization->appendChild($my_item);
10579
                }
10580
10581
                // Get the path of the file(s) from the course directory root.
10582
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10583
                $my_xml_file_path = $my_file_path;
10584
                if (!empty($path_to_remove)) {
10585
                    // From docs
10586
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10587
10588
                    // From quiz
10589
                    if ($this->ref === 'chamilo_scorm_export') {
10590
                        $path_to_remove = 'scorm/'.$this->path.'/';
10591
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10592
                    }
10593
                }
10594
10595
                $my_sub_dir = dirname($my_file_path);
10596
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10597
                $my_xml_sub_dir = $my_sub_dir;
10598
                // Give a <resource> child to the <resources> element
10599
                $my_resource = $xmldoc->createElement('resource');
10600
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10601
                $my_resource->setAttribute('type', 'webcontent');
10602
                $my_resource->setAttribute('href', $my_xml_file_path);
10603
                // adlcp:scormtype can be either 'sco' or 'asset'.
10604
                if ($item->type === 'sco') {
10605
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10606
                } else {
10607
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10608
                }
10609
                // xml:base is the base directory to find the files declared in this resource.
10610
                $my_resource->setAttribute('xml:base', '');
10611
                // Give a <file> child to the <resource> element.
10612
                $my_file = $xmldoc->createElement('file');
10613
                $my_file->setAttribute('href', $my_xml_file_path);
10614
                $my_resource->appendChild($my_file);
10615
10616
                // Dependency to other files - not yet supported.
10617
                $i = 1;
10618
                if ($inc_docs) {
10619
                    foreach ($inc_docs as $doc_info) {
10620
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10621
                            continue;
10622
                        }
10623
                        $my_dep = $xmldoc->createElement('resource');
10624
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10625
                        $my_dep->setAttribute('identifier', $res_id);
10626
                        $my_dep->setAttribute('type', 'webcontent');
10627
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10628
                        $my_dep_file = $xmldoc->createElement('file');
10629
                        // Check type of URL.
10630
                        if ($doc_info[1] == 'remote') {
10631
                            // Remote file. Save url as is.
10632
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10633
                            $my_dep->setAttribute('xml:base', '');
10634
                        } elseif ($doc_info[1] === 'local') {
10635
                            switch ($doc_info[2]) {
10636
                                case 'url':
10637
                                    // Local URL - save path as url for now, don't zip file.
10638
                                    $abs_path = api_get_path(SYS_PATH).
10639
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10640
                                    $current_dir = dirname($abs_path);
10641
                                    $current_dir = str_replace('\\', '/', $current_dir);
10642
                                    $file_path = realpath($abs_path);
10643
                                    $file_path = str_replace('\\', '/', $file_path);
10644
                                    $my_dep_file->setAttribute('href', $file_path);
10645
                                    $my_dep->setAttribute('xml:base', '');
10646
                                    if (strstr($file_path, $main_path) !== false) {
10647
                                        // The calculated real path is really inside Chamilo's root path.
10648
                                        // Reduce file path to what's under the DocumentRoot.
10649
                                        $replace = $file_path;
10650
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10651
                                        $destinationFile = $file_path;
10652
10653
                                        if (strstr($file_path, 'upload/users') !== false) {
10654
                                            $pos = strpos($file_path, 'my_files/');
10655
                                            if ($pos !== false) {
10656
                                                $onlyDirectory = str_replace(
10657
                                                    'upload/users/',
10658
                                                    '',
10659
                                                    substr($file_path, $pos, strlen($file_path))
10660
                                                );
10661
                                            }
10662
                                            $replace = $onlyDirectory;
10663
                                            $destinationFile = $replace;
10664
                                        }
10665
                                        $zip_files_abs[] = $file_path;
10666
                                        $link_updates[$my_file_path][] = [
10667
                                            'orig' => $doc_info[0],
10668
                                            'dest' => $destinationFile,
10669
                                            'replace' => $replace,
10670
                                        ];
10671
                                        $my_dep_file->setAttribute('href', $file_path);
10672
                                        $my_dep->setAttribute('xml:base', '');
10673
                                    } elseif (empty($file_path)) {
10674
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
10675
                                        $file_path = str_replace('//', '/', $file_path);
10676
                                        if (file_exists($file_path)) {
10677
                                            // We get the relative path.
10678
                                            $file_path = substr($file_path, strlen($current_dir));
10679
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
10680
                                            $link_updates[$my_file_path][] = [
10681
                                                'orig' => $doc_info[0],
10682
                                                'dest' => $file_path,
10683
                                            ];
10684
                                            $my_dep_file->setAttribute('href', $file_path);
10685
                                            $my_dep->setAttribute('xml:base', '');
10686
                                        }
10687
                                    }
10688
                                    break;
10689
                                case 'abs':
10690
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
10691
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10692
                                    $my_dep->setAttribute('xml:base', '');
10693
10694
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
10695
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
10696
                                    $abs_img_path_without_subdir = $doc_info[0];
10697
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
10698
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
10699
                                    if ($pos === 0) {
10700
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
10701
                                    }
10702
10703
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
10704
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
10705
10706
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
10707
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
10708
                                    // Check if the current document is in that path.
10709
                                    if (strstr($file_path, $cur_path) !== false) {
10710
                                        $destinationFile = substr($file_path, strlen($cur_path));
10711
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
10712
10713
                                        $fileToTest = $cur_path.$my_file_path;
10714
                                        if (!empty($path_to_remove)) {
10715
                                            $fileToTest = str_replace(
10716
                                                $path_to_remove.'/',
10717
                                                $path_to_replace,
10718
                                                $cur_path.$my_file_path
10719
                                            );
10720
                                        }
10721
10722
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
10723
10724
                                        // Put the current document in the zip (this array is the array
10725
                                        // that will manage documents already in the course folder - relative).
10726
                                        $zip_files[] = $filePathNoCoursePart;
10727
                                        // Update the links to the current document in the
10728
                                        // containing document (make them relative).
10729
                                        $link_updates[$my_file_path][] = [
10730
                                            'orig' => $doc_info[0],
10731
                                            'dest' => $destinationFile,
10732
                                            'replace' => $relative_path,
10733
                                        ];
10734
10735
                                        $my_dep_file->setAttribute('href', $file_path);
10736
                                        $my_dep->setAttribute('xml:base', '');
10737
                                    } elseif (strstr($file_path, $main_path) !== false) {
10738
                                        // The calculated real path is really inside Chamilo's root path.
10739
                                        // Reduce file path to what's under the DocumentRoot.
10740
                                        $file_path = substr($file_path, strlen($root_path));
10741
                                        $zip_files_abs[] = $file_path;
10742
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10743
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10744
                                        $my_dep->setAttribute('xml:base', '');
10745
                                    } elseif (empty($file_path)) {
10746
                                        // Probably this is an image inside "/main" directory
10747
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
10748
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10749
10750
                                        if (file_exists($file_path)) {
10751
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
10752
                                                // We get the relative path.
10753
                                                $pos = strpos($file_path, 'main/default_course_document/');
10754
                                                if ($pos !== false) {
10755
                                                    $onlyDirectory = str_replace(
10756
                                                        'main/default_course_document/',
10757
                                                        '',
10758
                                                        substr($file_path, $pos, strlen($file_path))
10759
                                                    );
10760
                                                }
10761
10762
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
10763
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
10764
                                                $zip_files_abs[] = $fileAbs;
10765
                                                $link_updates[$my_file_path][] = [
10766
                                                    'orig' => $doc_info[0],
10767
                                                    'dest' => $destinationFile,
10768
                                                ];
10769
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
10770
                                                $my_dep->setAttribute('xml:base', '');
10771
                                            }
10772
                                        }
10773
                                    }
10774
                                    break;
10775
                                case 'rel':
10776
                                    // Path relative to the current document.
10777
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
10778
                                    if (substr($doc_info[0], 0, 2) === '..') {
10779
                                        // Relative path going up.
10780
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
10781
                                        $current_dir = str_replace('\\', '/', $current_dir);
10782
                                        $file_path = realpath($current_dir.$doc_info[0]);
10783
                                        $file_path = str_replace('\\', '/', $file_path);
10784
                                        if (strstr($file_path, $main_path) !== false) {
10785
                                            // The calculated real path is really inside Chamilo's root path.
10786
                                            // Reduce file path to what's under the DocumentRoot.
10787
                                            $file_path = substr($file_path, strlen($root_path));
10788
                                            $zip_files_abs[] = $file_path;
10789
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
10790
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
10791
                                            $my_dep->setAttribute('xml:base', '');
10792
                                        }
10793
                                    } else {
10794
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
10795
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
10796
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
10797
                                    }
10798
                                    break;
10799
                                default:
10800
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
10801
                                    $my_dep->setAttribute('xml:base', '');
10802
                                    break;
10803
                            }
10804
                        }
10805
                        $my_dep->appendChild($my_dep_file);
10806
                        $resources->appendChild($my_dep);
10807
                        $dependency = $xmldoc->createElement('dependency');
10808
                        $dependency->setAttribute('identifierref', $res_id);
10809
                        $my_resource->appendChild($dependency);
10810
                        $i++;
10811
                    }
10812
                }
10813
                $resources->appendChild($my_resource);
10814
                $zip_files[] = $my_file_path;
10815
            } else {
10816
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
10817
                switch ($item->type) {
10818
                    case TOOL_LINK:
10819
                        $my_item = $xmldoc->createElement('item');
10820
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10821
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10822
                        $my_item->setAttribute('isvisible', 'true');
10823
                        // Give a child element <title> to the <item> element.
10824
                        $my_title = $xmldoc->createElement(
10825
                            'title',
10826
                            htmlspecialchars(
10827
                                api_utf8_encode($item->get_title()),
10828
                                ENT_QUOTES,
10829
                                'UTF-8'
10830
                            )
10831
                        );
10832
                        $my_item->appendChild($my_title);
10833
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10834
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10835
                        $my_prereqs->setAttribute('type', 'aicc_script');
10836
                        $my_item->appendChild($my_prereqs);
10837
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10838
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
10839
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10840
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
10841
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10842
                        //$xmldoc->createElement('adlcp:datafromlms', '');
10843
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10844
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10845
                        $my_item->appendChild($my_masteryscore);
10846
10847
                        // Attach this item to the organization element or its parent if there is one.
10848
                        if (!empty($item->parent) && $item->parent != 0) {
10849
                            $children = $organization->childNodes;
10850
                            for ($i = 0; $i < $children->length; $i++) {
10851
                                $item_temp = $children->item($i);
10852
                                if ($item_temp->nodeName == 'item') {
10853
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
10854
                                        $item_temp->appendChild($my_item);
10855
                                    }
10856
                                }
10857
                            }
10858
                        } else {
10859
                            $organization->appendChild($my_item);
10860
                        }
10861
10862
                        $my_file_path = 'link_'.$item->get_id().'.html';
10863
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
10864
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
10865
                        $rs = Database::query($sql);
10866
                        if ($link = Database::fetch_array($rs)) {
10867
                            $url = $link['url'];
10868
                            $title = stripslashes($link['title']);
10869
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
10870
                            $my_xml_file_path = $my_file_path;
10871
                            $my_sub_dir = dirname($my_file_path);
10872
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10873
                            $my_xml_sub_dir = $my_sub_dir;
10874
                            // Give a <resource> child to the <resources> element.
10875
                            $my_resource = $xmldoc->createElement('resource');
10876
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10877
                            $my_resource->setAttribute('type', 'webcontent');
10878
                            $my_resource->setAttribute('href', $my_xml_file_path);
10879
                            // adlcp:scormtype can be either 'sco' or 'asset'.
10880
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
10881
                            // xml:base is the base directory to find the files declared in this resource.
10882
                            $my_resource->setAttribute('xml:base', '');
10883
                            // give a <file> child to the <resource> element.
10884
                            $my_file = $xmldoc->createElement('file');
10885
                            $my_file->setAttribute('href', $my_xml_file_path);
10886
                            $my_resource->appendChild($my_file);
10887
                            $resources->appendChild($my_resource);
10888
                        }
10889
                        break;
10890
                    case TOOL_QUIZ:
10891
                        $exe_id = $item->path;
10892
                        // Should be using ref when everything will be cleaned up in this regard.
10893
                        $exe = new Exercise();
10894
                        $exe->read($exe_id);
10895
                        $my_item = $xmldoc->createElement('item');
10896
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
10897
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
10898
                        $my_item->setAttribute('isvisible', 'true');
10899
                        // Give a child element <title> to the <item> element.
10900
                        $my_title = $xmldoc->createElement(
10901
                            'title',
10902
                            htmlspecialchars(
10903
                                api_utf8_encode($item->get_title()),
10904
                                ENT_QUOTES,
10905
                                'UTF-8'
10906
                            )
10907
                        );
10908
                        $my_item->appendChild($my_title);
10909
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
10910
                        $my_item->appendChild($my_max_score);
10911
                        // Give a child element <adlcp:prerequisites> to the <item> element.
10912
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
10913
                        $my_prereqs->setAttribute('type', 'aicc_script');
10914
                        $my_item->appendChild($my_prereqs);
10915
                        // Give a child element <adlcp:masteryscore> to the <item> element.
10916
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10917
                        $my_item->appendChild($my_masteryscore);
10918
10919
                        // Attach this item to the organization element or hits parent if there is one.
10920
                        if (!empty($item->parent) && $item->parent != 0) {
10921
                            $children = $organization->childNodes;
10922
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10923
                            if ($possible_parent) {
10924
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
10925
                                    $possible_parent->appendChild($my_item);
10926
                                }
10927
                            }
10928
                        } else {
10929
                            $organization->appendChild($my_item);
10930
                        }
10931
10932
                        // Get the path of the file(s) from the course directory root
10933
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10934
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
10935
                        // Write the contents of the exported exercise into a (big) html file
10936
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
10937
                        $scormExercise = new ScormExercise($exe, true);
10938
                        $contents = $scormExercise->export();
10939
10940
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
10941
                        $res = file_put_contents($tmp_file_path, $contents);
10942
                        if ($res === false) {
10943
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
10944
                        }
10945
                        $files_cleanup[] = $tmp_file_path;
10946
                        $my_xml_file_path = $my_file_path;
10947
                        $my_sub_dir = dirname($my_file_path);
10948
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10949
                        $my_xml_sub_dir = $my_sub_dir;
10950
                        // Give a <resource> child to the <resources> element.
10951
                        $my_resource = $xmldoc->createElement('resource');
10952
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10953
                        $my_resource->setAttribute('type', 'webcontent');
10954
                        $my_resource->setAttribute('href', $my_xml_file_path);
10955
                        // adlcp:scormtype can be either 'sco' or 'asset'.
10956
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
10957
                        // xml:base is the base directory to find the files declared in this resource.
10958
                        $my_resource->setAttribute('xml:base', '');
10959
                        // Give a <file> child to the <resource> element.
10960
                        $my_file = $xmldoc->createElement('file');
10961
                        $my_file->setAttribute('href', $my_xml_file_path);
10962
                        $my_resource->appendChild($my_file);
10963
10964
                        // Get included docs.
10965
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
10966
10967
                        // Dependency to other files - not yet supported.
10968
                        $i = 1;
10969
                        foreach ($inc_docs as $doc_info) {
10970
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
10971
                                continue;
10972
                            }
10973
                            $my_dep = $xmldoc->createElement('resource');
10974
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10975
                            $my_dep->setAttribute('identifier', $res_id);
10976
                            $my_dep->setAttribute('type', 'webcontent');
10977
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
10978
                            $my_dep_file = $xmldoc->createElement('file');
10979
                            // Check type of URL.
10980
                            if ($doc_info[1] == 'remote') {
10981
                                // Remote file. Save url as is.
10982
                                $my_dep_file->setAttribute('href', $doc_info[0]);
10983
                                $my_dep->setAttribute('xml:base', '');
10984
                            } elseif ($doc_info[1] == 'local') {
10985
                                switch ($doc_info[2]) {
10986
                                    case 'url': // Local URL - save path as url for now, don't zip file.
10987
                                        // Save file but as local file (retrieve from URL).
10988
                                        $abs_path = api_get_path(SYS_PATH).
10989
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10990
                                        $current_dir = dirname($abs_path);
10991
                                        $current_dir = str_replace('\\', '/', $current_dir);
10992
                                        $file_path = realpath($abs_path);
10993
                                        $file_path = str_replace('\\', '/', $file_path);
10994
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
10995
                                        $my_dep->setAttribute('xml:base', '');
10996
                                        if (strstr($file_path, $main_path) !== false) {
10997
                                            // The calculated real path is really inside the chamilo root path.
10998
                                            // Reduce file path to what's under the DocumentRoot.
10999
                                            $file_path = substr($file_path, strlen($root_path));
11000
                                            $zip_files_abs[] = $file_path;
11001
                                            $link_updates[$my_file_path][] = [
11002
                                                'orig' => $doc_info[0],
11003
                                                'dest' => 'document/'.$file_path,
11004
                                            ];
11005
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11006
                                            $my_dep->setAttribute('xml:base', '');
11007
                                        } elseif (empty($file_path)) {
11008
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11009
                                            $file_path = str_replace('//', '/', $file_path);
11010
                                            if (file_exists($file_path)) {
11011
                                                $file_path = substr($file_path, strlen($current_dir));
11012
                                                // We get the relative path.
11013
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11014
                                                $link_updates[$my_file_path][] = [
11015
                                                    'orig' => $doc_info[0],
11016
                                                    'dest' => 'document/'.$file_path,
11017
                                                ];
11018
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11019
                                                $my_dep->setAttribute('xml:base', '');
11020
                                            }
11021
                                        }
11022
                                        break;
11023
                                    case 'abs':
11024
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11025
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11026
                                        $current_dir = str_replace('\\', '/', $current_dir);
11027
                                        $file_path = realpath($doc_info[0]);
11028
                                        $file_path = str_replace('\\', '/', $file_path);
11029
                                        $my_dep_file->setAttribute('href', $file_path);
11030
                                        $my_dep->setAttribute('xml:base', '');
11031
11032
                                        if (strstr($file_path, $main_path) !== false) {
11033
                                            // The calculated real path is really inside the chamilo root path.
11034
                                            // Reduce file path to what's under the DocumentRoot.
11035
                                            $file_path = substr($file_path, strlen($root_path));
11036
                                            $zip_files_abs[] = $file_path;
11037
                                            $link_updates[$my_file_path][] = [
11038
                                                'orig' => $doc_info[0],
11039
                                                'dest' => $file_path,
11040
                                            ];
11041
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11042
                                            $my_dep->setAttribute('xml:base', '');
11043
                                        } elseif (empty($file_path)) {
11044
                                            $docSysPartPath = str_replace(
11045
                                                api_get_path(REL_COURSE_PATH),
11046
                                                '',
11047
                                                $doc_info[0]
11048
                                            );
11049
11050
                                            $docSysPartPathNoCourseCode = str_replace(
11051
                                                $_course['directory'].'/',
11052
                                                '',
11053
                                                $docSysPartPath
11054
                                            );
11055
11056
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11057
                                            if (file_exists($docSysPath)) {
11058
                                                $file_path = $docSysPartPathNoCourseCode;
11059
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11060
                                                $link_updates[$my_file_path][] = [
11061
                                                    'orig' => $doc_info[0],
11062
                                                    'dest' => $file_path,
11063
                                                ];
11064
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11065
                                                $my_dep->setAttribute('xml:base', '');
11066
                                            }
11067
                                        }
11068
                                        break;
11069
                                    case 'rel':
11070
                                        // Path relative to the current document. Save xml:base as current document's
11071
                                        // directory and save file in zip as subdir.file_path
11072
                                        if (substr($doc_info[0], 0, 2) === '..') {
11073
                                            // Relative path going up.
11074
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11075
                                            $current_dir = str_replace('\\', '/', $current_dir);
11076
                                            $file_path = realpath($current_dir.$doc_info[0]);
11077
                                            $file_path = str_replace('\\', '/', $file_path);
11078
                                            if (strstr($file_path, $main_path) !== false) {
11079
                                                // The calculated real path is really inside Chamilo's root path.
11080
                                                // Reduce file path to what's under the DocumentRoot.
11081
11082
                                                $file_path = substr($file_path, strlen($root_path));
11083
                                                $file_path_dest = $file_path;
11084
11085
                                                // File path is courses/CHAMILO/document/....
11086
                                                $info_file_path = explode('/', $file_path);
11087
                                                if ($info_file_path[0] == 'courses') {
11088
                                                    // Add character "/" in file path.
11089
                                                    $file_path_dest = 'document/'.$file_path;
11090
                                                }
11091
                                                $zip_files_abs[] = $file_path;
11092
11093
                                                $link_updates[$my_file_path][] = [
11094
                                                    'orig' => $doc_info[0],
11095
                                                    'dest' => $file_path_dest,
11096
                                                ];
11097
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11098
                                                $my_dep->setAttribute('xml:base', '');
11099
                                            }
11100
                                        } else {
11101
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11102
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11103
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11104
                                        }
11105
                                        break;
11106
                                    default:
11107
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11108
                                        $my_dep->setAttribute('xml:base', '');
11109
                                        break;
11110
                                }
11111
                            }
11112
                            $my_dep->appendChild($my_dep_file);
11113
                            $resources->appendChild($my_dep);
11114
                            $dependency = $xmldoc->createElement('dependency');
11115
                            $dependency->setAttribute('identifierref', $res_id);
11116
                            $my_resource->appendChild($dependency);
11117
                            $i++;
11118
                        }
11119
                        $resources->appendChild($my_resource);
11120
                        $zip_files[] = $my_file_path;
11121
                        break;
11122
                    default:
11123
                        // Get the path of the file(s) from the course directory root
11124
                        $my_file_path = 'non_exportable.html';
11125
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11126
                        $my_xml_file_path = $my_file_path;
11127
                        $my_sub_dir = dirname($my_file_path);
11128
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11129
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11130
                        $my_xml_sub_dir = $my_sub_dir;
11131
                        // Give a <resource> child to the <resources> element.
11132
                        $my_resource = $xmldoc->createElement('resource');
11133
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11134
                        $my_resource->setAttribute('type', 'webcontent');
11135
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11136
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11137
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11138
                        // xml:base is the base directory to find the files declared in this resource.
11139
                        $my_resource->setAttribute('xml:base', '');
11140
                        // Give a <file> child to the <resource> element.
11141
                        $my_file = $xmldoc->createElement('file');
11142
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11143
                        $my_resource->appendChild($my_file);
11144
                        $resources->appendChild($my_resource);
11145
                        break;
11146
                }
11147
            }
11148
        }
11149
        $organizations->appendChild($organization);
11150
        $root->appendChild($organizations);
11151
        $root->appendChild($resources);
11152
        $xmldoc->appendChild($root);
11153
11154
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11155
11156
        // then add the file to the zip, then destroy the file (this is done automatically).
11157
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11158
        foreach ($zip_files as $file_path) {
11159
            if (empty($file_path)) {
11160
                continue;
11161
            }
11162
11163
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11164
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11165
11166
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11167
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11168
            }
11169
11170
            $this->create_path($dest_file);
11171
            @copy($filePath, $dest_file);
11172
11173
            // Check if the file needs a link update.
11174
            if (in_array($file_path, array_keys($link_updates))) {
11175
                $string = file_get_contents($dest_file);
11176
                unlink($dest_file);
11177
                foreach ($link_updates[$file_path] as $old_new) {
11178
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11179
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11180
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11181
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11182
                    if (substr($old_new['dest'], -3) === 'flv' &&
11183
                        substr($old_new['dest'], 0, 5) === 'main/'
11184
                    ) {
11185
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11186
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11187
                        substr($old_new['dest'], 0, 6) === 'video/'
11188
                    ) {
11189
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11190
                    }
11191
11192
                    // Fix to avoid problems with default_course_document
11193
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11194
                        $newDestination = $old_new['dest'];
11195
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11196
                            $newDestination = $old_new['replace'];
11197
                        }
11198
                    } else {
11199
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11200
                    }
11201
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11202
11203
                    // Add files inside the HTMLs
11204
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11205
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11206
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11207
                        copy(
11208
                            $sys_course_path.$new_path,
11209
                            $destinationFile
11210
                        );
11211
                    }
11212
                }
11213
                file_put_contents($dest_file, $string);
11214
            }
11215
11216
            if (file_exists($filePath) && $copyAll) {
11217
                $extension = $this->get_extension($filePath);
11218
                if (in_array($extension, ['html', 'html'])) {
11219
                    $containerOrigin = dirname($filePath);
11220
                    $containerDestination = dirname($dest_file);
11221
11222
                    $finder = new Finder();
11223
                    $finder->files()->in($containerOrigin)
11224
                        ->notName('*_DELETED_*')
11225
                        ->exclude('share_folder')
11226
                        ->exclude('chat_files')
11227
                        ->exclude('certificates')
11228
                    ;
11229
11230
                    if (is_dir($containerOrigin) &&
11231
                        is_dir($containerDestination)
11232
                    ) {
11233
                        $fs = new Filesystem();
11234
                        $fs->mirror(
11235
                            $containerOrigin,
11236
                            $containerDestination,
11237
                            $finder
11238
                        );
11239
                    }
11240
                }
11241
            }
11242
        }
11243
11244
        foreach ($zip_files_abs as $file_path) {
11245
            if (empty($file_path)) {
11246
                continue;
11247
            }
11248
11249
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11250
                continue;
11251
            }
11252
11253
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11254
            if (strstr($file_path, 'upload/users') !== false) {
11255
                $pos = strpos($file_path, 'my_files/');
11256
                if ($pos !== false) {
11257
                    $onlyDirectory = str_replace(
11258
                        'upload/users/',
11259
                        '',
11260
                        substr($file_path, $pos, strlen($file_path))
11261
                    );
11262
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11263
                }
11264
            }
11265
11266
            if (strstr($file_path, 'default_course_document/') !== false) {
11267
                $replace = str_replace('/main', '', $file_path);
11268
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11269
            }
11270
11271
            if (empty($dest_file)) {
11272
                continue;
11273
            }
11274
11275
            $this->create_path($dest_file);
11276
            copy($main_path.$file_path, $dest_file);
11277
            // Check if the file needs a link update.
11278
            if (in_array($file_path, array_keys($link_updates))) {
11279
                $string = file_get_contents($dest_file);
11280
                unlink($dest_file);
11281
                foreach ($link_updates[$file_path] as $old_new) {
11282
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11283
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11284
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11285
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11286
                    if (substr($old_new['dest'], -3) == 'flv' &&
11287
                        substr($old_new['dest'], 0, 5) == 'main/'
11288
                    ) {
11289
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11290
                    }
11291
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11292
                }
11293
                file_put_contents($dest_file, $string);
11294
            }
11295
        }
11296
11297
        if (is_array($links_to_create)) {
11298
            foreach ($links_to_create as $file => $link) {
11299
                $content = '<!DOCTYPE html><head>
11300
                            <meta charset="'.api_get_language_isocode().'" />
11301
                            <title>'.$link['title'].'</title>
11302
                            </head>
11303
                            <body dir="'.api_get_text_direction().'">
11304
                            <div style="text-align:center">
11305
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11306
                            </body>
11307
                            </html>';
11308
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11309
            }
11310
        }
11311
11312
        // Add non exportable message explanation.
11313
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11314
        $file_content = '<!DOCTYPE html><head>
11315
                        <meta charset="'.api_get_language_isocode().'" />
11316
                        <title>'.$lang_not_exportable.'</title>
11317
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11318
                        </head>
11319
                        <body dir="'.api_get_text_direction().'">';
11320
        $file_content .=
11321
            <<<EOD
11322
                    <style>
11323
            .error-message {
11324
                font-family: arial, verdana, helvetica, sans-serif;
11325
                border-width: 1px;
11326
                border-style: solid;
11327
                left: 50%;
11328
                margin: 10px auto;
11329
                min-height: 30px;
11330
                padding: 5px;
11331
                right: 50%;
11332
                width: 500px;
11333
                background-color: #FFD1D1;
11334
                border-color: #FF0000;
11335
                color: #000;
11336
            }
11337
        </style>
11338
    <body>
11339
        <div class="error-message">
11340
            $lang_not_exportable
11341
        </div>
11342
    </body>
11343
</html>
11344
EOD;
11345
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11346
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11347
        }
11348
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11349
11350
        // Add the extra files that go along with a SCORM package.
11351
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11352
11353
        $fs = new Filesystem();
11354
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11355
11356
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11357
        $manifest = @$xmldoc->saveXML();
11358
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11359
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11360
        $zip_folder->add(
11361
            $archivePath.'/'.$temp_dir_short,
11362
            PCLZIP_OPT_REMOVE_PATH,
11363
            $archivePath.'/'.$temp_dir_short.'/'
11364
        );
11365
11366
        // Clean possible temporary files.
11367
        foreach ($files_cleanup as $file) {
11368
            $res = unlink($file);
11369
            if ($res === false) {
11370
                error_log(
11371
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11372
                    0
11373
                );
11374
            }
11375
        }
11376
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11377
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11378
    }
11379
11380
    /**
11381
     * @param int $lp_id
11382
     *
11383
     * @return bool
11384
     */
11385
    public function scorm_export_to_pdf($lp_id)
11386
    {
11387
        $lp_id = (int) $lp_id;
11388
        $files_to_export = [];
11389
11390
        $sessionId = api_get_session_id();
11391
        $course_data = api_get_course_info($this->cc);
11392
11393
        if (!empty($course_data)) {
11394
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11395
            $list = self::get_flat_ordered_items_list($lp_id);
11396
            if (!empty($list)) {
11397
                foreach ($list as $item_id) {
11398
                    $item = $this->items[$item_id];
11399
                    switch ($item->type) {
11400
                        case 'document':
11401
                            // Getting documents from a LP with chamilo documents
11402
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11403
                            // Try loading document from the base course.
11404
                            if (empty($file_data) && !empty($sessionId)) {
11405
                                $file_data = DocumentManager::get_document_data_by_id(
11406
                                    $item->path,
11407
                                    $this->cc,
11408
                                    false,
11409
                                    0
11410
                                );
11411
                            }
11412
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11413
                            if (file_exists($file_path)) {
11414
                                $files_to_export[] = [
11415
                                    'title' => $item->get_title(),
11416
                                    'path' => $file_path,
11417
                                ];
11418
                            }
11419
                            break;
11420
                        case 'asset': //commes from a scorm package generated by chamilo
11421
                        case 'sco':
11422
                            $file_path = $scorm_path.'/'.$item->path;
11423
                            if (file_exists($file_path)) {
11424
                                $files_to_export[] = [
11425
                                    'title' => $item->get_title(),
11426
                                    'path' => $file_path,
11427
                                ];
11428
                            }
11429
                            break;
11430
                        case 'dir':
11431
                            $files_to_export[] = [
11432
                                'title' => $item->get_title(),
11433
                                'path' => null,
11434
                            ];
11435
                            break;
11436
                    }
11437
                }
11438
            }
11439
11440
            $pdf = new PDF();
11441
            $result = $pdf->html_to_pdf(
11442
                $files_to_export,
11443
                $this->name,
11444
                $this->cc,
11445
                true,
11446
                true,
11447
                true,
11448
                $this->get_name()
11449
            );
11450
11451
            return $result;
11452
        }
11453
11454
        return false;
11455
    }
11456
11457
    /**
11458
     * Temp function to be moved in main_api or the best place around for this.
11459
     * Creates a file path if it doesn't exist.
11460
     *
11461
     * @param string $path
11462
     */
11463
    public function create_path($path)
11464
    {
11465
        $path_bits = explode('/', dirname($path));
11466
11467
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11468
        $path_built = IS_WINDOWS_OS ? '' : '/';
11469
        foreach ($path_bits as $bit) {
11470
            if (!empty($bit)) {
11471
                $new_path = $path_built.$bit;
11472
                if (is_dir($new_path)) {
11473
                    $path_built = $new_path.'/';
11474
                } else {
11475
                    mkdir($new_path, api_get_permissions_for_new_directories());
11476
                    $path_built = $new_path.'/';
11477
                }
11478
            }
11479
        }
11480
    }
11481
11482
    /**
11483
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11484
     *
11485
     * @return bool The results of the unlink function, or false if there was no image to start with
11486
     */
11487
    public function delete_lp_image()
11488
    {
11489
        $img = $this->get_preview_image();
11490
        if ($img != '') {
11491
            $del_file = $this->get_preview_image_path(null, 'sys');
11492
            if (isset($del_file) && file_exists($del_file)) {
11493
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11494
                if (file_exists($del_file_2)) {
11495
                    unlink($del_file_2);
11496
                }
11497
                $this->set_preview_image('');
11498
11499
                return @unlink($del_file);
11500
            }
11501
        }
11502
11503
        return false;
11504
    }
11505
11506
    /**
11507
     * Uploads an author image to the upload/learning_path/images directory.
11508
     *
11509
     * @param array    The image array, coming from the $_FILES superglobal
11510
     *
11511
     * @return bool True on success, false on error
11512
     */
11513
    public function upload_image($image_array)
11514
    {
11515
        if (!empty($image_array['name'])) {
11516
            $upload_ok = process_uploaded_file($image_array);
11517
            $has_attachment = true;
11518
        }
11519
11520
        if ($upload_ok && $has_attachment) {
11521
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11522
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11523
            $updir = $sys_course_path.$courseDir;
11524
            // Try to add an extension to the file if it hasn't one.
11525
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11526
11527
            if (filter_extension($new_file_name)) {
11528
                $file_extension = explode('.', $image_array['name']);
11529
                $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]);
11530
                $filename = uniqid('');
11531
                $new_file_name = $filename.'.'.$file_extension;
11532
                $new_path = $updir.'/'.$new_file_name;
11533
11534
                // Resize the image.
11535
                $temp = new Image($image_array['tmp_name']);
11536
                $temp->resize(104);
11537
                $result = $temp->send_image($new_path);
11538
11539
                // Storing the image filename.
11540
                if ($result) {
11541
                    $this->set_preview_image($new_file_name);
11542
11543
                    //Resize to 64px to use on course homepage
11544
                    $temp->resize(64);
11545
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11546
11547
                    return true;
11548
                }
11549
            }
11550
        }
11551
11552
        return false;
11553
    }
11554
11555
    /**
11556
     * @param int    $lp_id
11557
     * @param string $status
11558
     */
11559
    public function set_autolaunch($lp_id, $status)
11560
    {
11561
        $course_id = api_get_course_int_id();
11562
        $lp_id = (int) $lp_id;
11563
        $status = (int) $status;
11564
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11565
11566
        // Setting everything to autolaunch = 0
11567
        $attributes['autolaunch'] = 0;
11568
        $where = [
11569
            'session_id = ? AND c_id = ? ' => [
11570
                api_get_session_id(),
11571
                $course_id,
11572
            ],
11573
        ];
11574
        Database::update($lp_table, $attributes, $where);
11575
        if ($status == 1) {
11576
            //Setting my lp_id to autolaunch = 1
11577
            $attributes['autolaunch'] = 1;
11578
            $where = [
11579
                'iid = ? AND session_id = ? AND c_id = ?' => [
11580
                    $lp_id,
11581
                    api_get_session_id(),
11582
                    $course_id,
11583
                ],
11584
            ];
11585
            Database::update($lp_table, $attributes, $where);
11586
        }
11587
    }
11588
11589
    /**
11590
     * Gets previous_item_id for the next element of the lp_item table.
11591
     *
11592
     * @author Isaac flores paz
11593
     *
11594
     * @return int Previous item ID
11595
     */
11596
    public function select_previous_item_id()
11597
    {
11598
        $course_id = api_get_course_int_id();
11599
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11600
11601
        // Get the max order of the items
11602
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11603
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11604
        $rs_max_order = Database::query($sql);
11605
        $row_max_order = Database::fetch_object($rs_max_order);
11606
        $max_order = $row_max_order->display_order;
11607
        // Get the previous item ID
11608
        $sql = "SELECT iid as previous FROM $table_lp_item
11609
                WHERE 
11610
                    c_id = $course_id AND 
11611
                    lp_id = ".$this->lp_id." AND 
11612
                    display_order = '$max_order' ";
11613
        $rs_max = Database::query($sql);
11614
        $row_max = Database::fetch_object($rs_max);
11615
11616
        // Return the previous item ID
11617
        return $row_max->previous;
11618
    }
11619
11620
    /**
11621
     * Copies an LP.
11622
     */
11623
    public function copy()
11624
    {
11625
        // Course builder
11626
        $cb = new CourseBuilder();
11627
11628
        //Setting tools that will be copied
11629
        $cb->set_tools_to_build(['learnpaths']);
11630
11631
        //Setting elements that will be copied
11632
        $cb->set_tools_specific_id_list(
11633
            ['learnpaths' => [$this->lp_id]]
11634
        );
11635
11636
        $course = $cb->build();
11637
11638
        //Course restorer
11639
        $course_restorer = new CourseRestorer($course);
11640
        $course_restorer->set_add_text_in_items(true);
11641
        $course_restorer->set_tool_copy_settings(
11642
            ['learnpaths' => ['reset_dates' => true]]
11643
        );
11644
        $course_restorer->restore(
11645
            api_get_course_id(),
11646
            api_get_session_id(),
11647
            false,
11648
            false
11649
        );
11650
    }
11651
11652
    /**
11653
     * Verify document size.
11654
     *
11655
     * @param string $s
11656
     *
11657
     * @return bool
11658
     */
11659
    public static function verify_document_size($s)
11660
    {
11661
        $post_max = ini_get('post_max_size');
11662
        if (substr($post_max, -1, 1) == 'M') {
11663
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11664
        } elseif (substr($post_max, -1, 1) == 'G') {
11665
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11666
        }
11667
        $upl_max = ini_get('upload_max_filesize');
11668
        if (substr($upl_max, -1, 1) == 'M') {
11669
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11670
        } elseif (substr($upl_max, -1, 1) == 'G') {
11671
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11672
        }
11673
11674
        $repo = Container::$container->get('Chamilo\CourseBundle\Repository\CDocumentRepository');
11675
        $documents_total_space = $repo->getTotalSpace(api_get_course_int_id());
11676
11677
        $course_max_space = DocumentManager::get_course_quota();
11678
        $total_size = filesize($s) + $documents_total_space;
11679
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
11680
            return true;
11681
        }
11682
11683
        return false;
11684
    }
11685
11686
    /**
11687
     * Clear LP prerequisites.
11688
     */
11689
    public function clear_prerequisites()
11690
    {
11691
        $course_id = $this->get_course_int_id();
11692
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11693
        $lp_id = $this->get_id();
11694
        // Cleaning prerequisites
11695
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
11696
                WHERE c_id = $course_id AND lp_id = $lp_id";
11697
        Database::query($sql);
11698
11699
        // Cleaning mastery score for exercises
11700
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
11701
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
11702
        Database::query($sql);
11703
    }
11704
11705
    public function set_previous_step_as_prerequisite_for_all_items()
11706
    {
11707
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11708
        $course_id = $this->get_course_int_id();
11709
        $lp_id = $this->get_id();
11710
11711
        if (!empty($this->items)) {
11712
            $previous_item_id = null;
11713
            $previous_item_max = 0;
11714
            $previous_item_type = null;
11715
            $last_item_not_dir = null;
11716
            $last_item_not_dir_type = null;
11717
            $last_item_not_dir_max = null;
11718
11719
            foreach ($this->ordered_items as $itemId) {
11720
                $item = $this->getItem($itemId);
11721
                // if there was a previous item... (otherwise jump to set it)
11722
                if (!empty($previous_item_id)) {
11723
                    $current_item_id = $item->get_id(); //save current id
11724
                    if ($item->get_type() != 'dir') {
11725
                        // Current item is not a folder, so it qualifies to get a prerequisites
11726
                        if ($last_item_not_dir_type == 'quiz') {
11727
                            // if previous is quiz, mark its max score as default score to be achieved
11728
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
11729
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
11730
                            Database::query($sql);
11731
                        }
11732
                        // now simply update the prerequisite to set it to the last non-chapter item
11733
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
11734
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
11735
                        Database::query($sql);
11736
                        // record item as 'non-chapter' reference
11737
                        $last_item_not_dir = $item->get_id();
11738
                        $last_item_not_dir_type = $item->get_type();
11739
                        $last_item_not_dir_max = $item->get_max();
11740
                    }
11741
                } else {
11742
                    if ($item->get_type() != 'dir') {
11743
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
11744
                        $last_item_not_dir = $item->get_id();
11745
                        $last_item_not_dir_type = $item->get_type();
11746
                        $last_item_not_dir_max = $item->get_max();
11747
                    }
11748
                }
11749
                // Saving the item as "previous item" for the next loop
11750
                $previous_item_id = $item->get_id();
11751
                $previous_item_max = $item->get_max();
11752
                $previous_item_type = $item->get_type();
11753
            }
11754
        }
11755
    }
11756
11757
    /**
11758
     * @param array $params
11759
     *
11760
     * @throws \Doctrine\ORM\OptimisticLockException
11761
     *
11762
     * @return int
11763
     */
11764
    public static function createCategory($params)
11765
    {
11766
        $em = Database::getManager();
11767
        $item = new CLpCategory();
11768
        $item->setName($params['name']);
11769
        $item->setCId($params['c_id']);
11770
        $em->persist($item);
11771
        $em->flush();
11772
11773
        api_item_property_update(
11774
            api_get_course_info(),
11775
            TOOL_LEARNPATH_CATEGORY,
11776
            $item->getId(),
11777
            'visible',
11778
            api_get_user_id()
11779
        );
11780
11781
        return $item->getId();
11782
    }
11783
11784
    /**
11785
     * @param array $params
11786
     *
11787
     * @throws \Doctrine\ORM\ORMException
11788
     * @throws \Doctrine\ORM\OptimisticLockException
11789
     * @throws \Doctrine\ORM\TransactionRequiredException
11790
     */
11791
    public static function updateCategory($params)
11792
    {
11793
        $em = Database::getManager();
11794
        /** @var CLpCategory $item */
11795
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $params['id']);
11796
        if ($item) {
11797
            $item->setName($params['name']);
11798
            $em->merge($item);
11799
            $em->flush();
11800
        }
11801
    }
11802
11803
    /**
11804
     * @param int $id
11805
     *
11806
     * @throws \Doctrine\ORM\ORMException
11807
     * @throws \Doctrine\ORM\OptimisticLockException
11808
     * @throws \Doctrine\ORM\TransactionRequiredException
11809
     */
11810
    public static function moveUpCategory($id)
11811
    {
11812
        $id = (int) $id;
11813
        $em = Database::getManager();
11814
        /** @var CLpCategory $item */
11815
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11816
        if ($item) {
11817
            $position = $item->getPosition() - 1;
11818
            $item->setPosition($position);
11819
            $em->persist($item);
11820
            $em->flush();
11821
        }
11822
    }
11823
11824
    /**
11825
     * @param int $id
11826
     *
11827
     * @throws \Doctrine\ORM\ORMException
11828
     * @throws \Doctrine\ORM\OptimisticLockException
11829
     * @throws \Doctrine\ORM\TransactionRequiredException
11830
     */
11831
    public static function moveDownCategory($id)
11832
    {
11833
        $id = (int) $id;
11834
        $em = Database::getManager();
11835
        /** @var CLpCategory $item */
11836
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11837
        if ($item) {
11838
            $position = $item->getPosition() + 1;
11839
            $item->setPosition($position);
11840
            $em->persist($item);
11841
            $em->flush();
11842
        }
11843
    }
11844
11845
    /**
11846
     * @param int $courseId
11847
     *
11848
     * @throws \Doctrine\ORM\Query\QueryException
11849
     *
11850
     * @return int|mixed
11851
     */
11852
    public static function getCountCategories($courseId)
11853
    {
11854
        if (empty($courseId)) {
11855
            return 0;
11856
        }
11857
        $em = Database::getManager();
11858
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
11859
        $query->setParameter('id', $courseId);
11860
11861
        return $query->getSingleScalarResult();
11862
    }
11863
11864
    /**
11865
     * @param int $courseId
11866
     *
11867
     * @return mixed
11868
     */
11869
    public static function getCategories($courseId)
11870
    {
11871
        $em = Database::getManager();
11872
11873
        // Using doctrine extensions
11874
        /** @var SortableRepository $repo */
11875
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
11876
        $items = $repo
11877
            ->getBySortableGroupsQuery(['cId' => $courseId])
11878
            ->getResult();
11879
11880
        return $items;
11881
    }
11882
11883
    /**
11884
     * @param int $id
11885
     *
11886
     * @throws \Doctrine\ORM\ORMException
11887
     * @throws \Doctrine\ORM\OptimisticLockException
11888
     * @throws \Doctrine\ORM\TransactionRequiredException
11889
     *
11890
     * @return CLpCategory
11891
     */
11892
    public static function getCategory($id)
11893
    {
11894
        $id = (int) $id;
11895
        $em = Database::getManager();
11896
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11897
11898
        return $item;
11899
    }
11900
11901
    /**
11902
     * @param int $courseId
11903
     *
11904
     * @return array
11905
     */
11906
    public static function getCategoryByCourse($courseId)
11907
    {
11908
        $em = Database::getManager();
11909
        $items = $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(
11910
            ['cId' => $courseId]
11911
        );
11912
11913
        return $items;
11914
    }
11915
11916
    /**
11917
     * @param int $id
11918
     *
11919
     * @throws \Doctrine\ORM\ORMException
11920
     * @throws \Doctrine\ORM\OptimisticLockException
11921
     * @throws \Doctrine\ORM\TransactionRequiredException
11922
     *
11923
     * @return mixed
11924
     */
11925
    public static function deleteCategory($id)
11926
    {
11927
        $em = Database::getManager();
11928
        $item = $em->find('ChamiloCourseBundle:CLpCategory', $id);
11929
        if ($item) {
11930
            $courseId = $item->getCId();
11931
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
11932
            $query->setParameter('id', $courseId);
11933
            $query->setParameter('catId', $item->getId());
11934
            $lps = $query->getResult();
11935
11936
            // Setting category = 0.
11937
            if ($lps) {
11938
                foreach ($lps as $lpItem) {
11939
                    $lpItem->setCategoryId(0);
11940
                }
11941
            }
11942
11943
            // Removing category.
11944
            $em->remove($item);
11945
            $em->flush();
11946
11947
            $courseInfo = api_get_course_info_by_id($courseId);
11948
            $sessionId = api_get_session_id();
11949
11950
            // Delete link tool
11951
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
11952
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
11953
            // Delete tools
11954
            $sql = "DELETE FROM $tbl_tool
11955
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
11956
            Database::query($sql);
11957
11958
            return true;
11959
        }
11960
11961
        return false;
11962
    }
11963
11964
    /**
11965
     * @param int  $courseId
11966
     * @param bool $addSelectOption
11967
     *
11968
     * @return mixed
11969
     */
11970
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
11971
    {
11972
        $items = self::getCategoryByCourse($courseId);
11973
        $cats = [];
11974
        if ($addSelectOption) {
11975
            $cats = [get_lang('SelectACategory')];
11976
        }
11977
11978
        if (!empty($items)) {
11979
            foreach ($items as $cat) {
11980
                $cats[$cat->getId()] = $cat->getName();
11981
            }
11982
        }
11983
11984
        return $cats;
11985
    }
11986
11987
    /**
11988
     * @param string $courseCode
11989
     * @param int    $lpId
11990
     * @param int    $user_id
11991
     *
11992
     * @return learnpath
11993
     */
11994
    public static function getLpFromSession($courseCode, $lpId, $user_id)
11995
    {
11996
        $debug = 0;
11997
        $learnPath = null;
11998
        $lpObject = Session::read('lpobject');
11999
        if ($lpObject !== null) {
12000
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12001
            if ($debug) {
12002
                error_log('getLpFromSession: unserialize');
12003
                error_log('------getLpFromSession------');
12004
                error_log('------unserialize------');
12005
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12006
                error_log("api_get_sessionid: ".api_get_session_id());
12007
            }
12008
        }
12009
12010
        if (!is_object($learnPath)) {
12011
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12012
            if ($debug) {
12013
                error_log('------getLpFromSession------');
12014
                error_log('getLpFromSession: create new learnpath');
12015
                error_log("create new LP with $courseCode - $lpId - $user_id");
12016
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12017
                error_log("api_get_sessionid: ".api_get_session_id());
12018
            }
12019
        }
12020
12021
        return $learnPath;
12022
    }
12023
12024
    /**
12025
     * @param int $itemId
12026
     *
12027
     * @return learnpathItem|false
12028
     */
12029
    public function getItem($itemId)
12030
    {
12031
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12032
            return $this->items[$itemId];
12033
        }
12034
12035
        return false;
12036
    }
12037
12038
    /**
12039
     * @return int
12040
     */
12041
    public function getCurrentAttempt()
12042
    {
12043
        $attempt = $this->getItem($this->get_current_item_id());
12044
        if ($attempt) {
12045
            $attemptId = $attempt->get_attempt_id();
12046
12047
            return $attemptId;
12048
        }
12049
12050
        return 0;
12051
    }
12052
12053
    /**
12054
     * @return int
12055
     */
12056
    public function getCategoryId()
12057
    {
12058
        return (int) $this->categoryId;
12059
    }
12060
12061
    /**
12062
     * @param int $categoryId
12063
     *
12064
     * @return bool
12065
     */
12066
    public function setCategoryId($categoryId)
12067
    {
12068
        $this->categoryId = (int) $categoryId;
12069
        $table = Database::get_course_table(TABLE_LP_MAIN);
12070
        $lp_id = $this->get_id();
12071
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12072
                WHERE iid = $lp_id";
12073
        Database::query($sql);
12074
12075
        return true;
12076
    }
12077
12078
    /**
12079
     * Get whether this is a learning path with the possibility to subscribe
12080
     * users or not.
12081
     *
12082
     * @return int
12083
     */
12084
    public function getSubscribeUsers()
12085
    {
12086
        return $this->subscribeUsers;
12087
    }
12088
12089
    /**
12090
     * Set whether this is a learning path with the possibility to subscribe
12091
     * users or not.
12092
     *
12093
     * @param int $value (0 = false, 1 = true)
12094
     *
12095
     * @return bool
12096
     */
12097
    public function setSubscribeUsers($value)
12098
    {
12099
        $this->subscribeUsers = (int) $value;
12100
        $table = Database::get_course_table(TABLE_LP_MAIN);
12101
        $lp_id = $this->get_id();
12102
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12103
                WHERE iid = $lp_id";
12104
        Database::query($sql);
12105
12106
        return true;
12107
    }
12108
12109
    /**
12110
     * Calculate the count of stars for a user in this LP
12111
     * This calculation is based on the following rules:
12112
     * - the student gets one star when he gets to 50% of the learning path
12113
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12114
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12115
     * - the student gets the final star when the score for the *last* test is >= 80%.
12116
     *
12117
     * @param int $sessionId Optional. The session ID
12118
     *
12119
     * @return int The count of stars
12120
     */
12121
    public function getCalculateStars($sessionId = 0)
12122
    {
12123
        $stars = 0;
12124
        $progress = self::getProgress(
12125
            $this->lp_id,
12126
            $this->user_id,
12127
            $this->course_int_id,
12128
            $sessionId
12129
        );
12130
12131
        if ($progress >= 50) {
12132
            $stars++;
12133
        }
12134
12135
        // Calculate stars chapters evaluation
12136
        $exercisesItems = $this->getExercisesItems();
12137
12138
        if (!empty($exercisesItems)) {
12139
            $totalResult = 0;
12140
12141
            foreach ($exercisesItems as $exerciseItem) {
12142
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12143
                    $this->user_id,
12144
                    $exerciseItem->path,
12145
                    $this->course_int_id,
12146
                    $sessionId,
12147
                    $this->lp_id,
12148
                    $exerciseItem->db_id
12149
                );
12150
12151
                $exerciseResultInfo = end($exerciseResultInfo);
12152
12153
                if (!$exerciseResultInfo) {
12154
                    continue;
12155
                }
12156
12157
                if (!empty($exerciseResultInfo['max_score'])) {
12158
                    $exerciseResult = $exerciseResultInfo['score'] * 100 / $exerciseResultInfo['max_score'];
12159
                } else {
12160
                    $exerciseResult = 0;
12161
                }
12162
                $totalResult += $exerciseResult;
12163
            }
12164
12165
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12166
12167
            if ($totalExerciseAverage >= 50) {
12168
                $stars++;
12169
            }
12170
12171
            if ($totalExerciseAverage >= 80) {
12172
                $stars++;
12173
            }
12174
        }
12175
12176
        // Calculate star for final evaluation
12177
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12178
12179
        if (!empty($finalEvaluationItem)) {
12180
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12181
                $this->user_id,
12182
                $finalEvaluationItem->path,
12183
                $this->course_int_id,
12184
                $sessionId,
12185
                $this->lp_id,
12186
                $finalEvaluationItem->db_id
12187
            );
12188
12189
            $evaluationResultInfo = end($evaluationResultInfo);
12190
12191
            if ($evaluationResultInfo) {
12192
                $evaluationResult = $evaluationResultInfo['score'] * 100 / $evaluationResultInfo['max_score'];
12193
12194
                if ($evaluationResult >= 80) {
12195
                    $stars++;
12196
                }
12197
            }
12198
        }
12199
12200
        return $stars;
12201
    }
12202
12203
    /**
12204
     * Get the items of exercise type.
12205
     *
12206
     * @return array The items. Otherwise return false
12207
     */
12208
    public function getExercisesItems()
12209
    {
12210
        $exercises = [];
12211
        foreach ($this->items as $item) {
12212
            if ($item->type != 'quiz') {
12213
                continue;
12214
            }
12215
            $exercises[] = $item;
12216
        }
12217
12218
        array_pop($exercises);
12219
12220
        return $exercises;
12221
    }
12222
12223
    /**
12224
     * Get the item of exercise type (evaluation type).
12225
     *
12226
     * @return array The final evaluation. Otherwise return false
12227
     */
12228
    public function getFinalEvaluationItem()
12229
    {
12230
        $exercises = [];
12231
        foreach ($this->items as $item) {
12232
            if ($item->type != 'quiz') {
12233
                continue;
12234
            }
12235
12236
            $exercises[] = $item;
12237
        }
12238
12239
        return array_pop($exercises);
12240
    }
12241
12242
    /**
12243
     * Calculate the total points achieved for the current user in this learning path.
12244
     *
12245
     * @param int $sessionId Optional. The session Id
12246
     *
12247
     * @return int
12248
     */
12249
    public function getCalculateScore($sessionId = 0)
12250
    {
12251
        // Calculate stars chapters evaluation
12252
        $exercisesItems = $this->getExercisesItems();
12253
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12254
        $totalExercisesResult = 0;
12255
        $totalEvaluationResult = 0;
12256
12257
        if ($exercisesItems !== false) {
12258
            foreach ($exercisesItems as $exerciseItem) {
12259
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12260
                    $this->user_id,
12261
                    $exerciseItem->path,
12262
                    $this->course_int_id,
12263
                    $sessionId,
12264
                    $this->lp_id,
12265
                    $exerciseItem->db_id
12266
                );
12267
12268
                $exerciseResultInfo = end($exerciseResultInfo);
12269
12270
                if (!$exerciseResultInfo) {
12271
                    continue;
12272
                }
12273
12274
                $totalExercisesResult += $exerciseResultInfo['score'];
12275
            }
12276
        }
12277
12278
        if (!empty($finalEvaluationItem)) {
12279
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12280
                $this->user_id,
12281
                $finalEvaluationItem->path,
12282
                $this->course_int_id,
12283
                $sessionId,
12284
                $this->lp_id,
12285
                $finalEvaluationItem->db_id
12286
            );
12287
12288
            $evaluationResultInfo = end($evaluationResultInfo);
12289
12290
            if ($evaluationResultInfo) {
12291
                $totalEvaluationResult += $evaluationResultInfo['score'];
12292
            }
12293
        }
12294
12295
        return $totalExercisesResult + $totalEvaluationResult;
12296
    }
12297
12298
    /**
12299
     * Check if URL is not allowed to be show in a iframe.
12300
     *
12301
     * @param string $src
12302
     *
12303
     * @return string
12304
     */
12305
    public function fixBlockedLinks($src)
12306
    {
12307
        $urlInfo = parse_url($src);
12308
12309
        $platformProtocol = 'https';
12310
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12311
            $platformProtocol = 'http';
12312
        }
12313
12314
        $protocolFixApplied = false;
12315
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12316
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12317
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12318
12319
        if ($platformProtocol != $scheme) {
12320
            Session::write('x_frame_source', $src);
12321
            $src = 'blank.php?error=x_frames_options';
12322
            $protocolFixApplied = true;
12323
        }
12324
12325
        if ($protocolFixApplied == false) {
12326
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12327
                // Check X-Frame-Options
12328
                $ch = curl_init();
12329
                $options = [
12330
                    CURLOPT_URL => $src,
12331
                    CURLOPT_RETURNTRANSFER => true,
12332
                    CURLOPT_HEADER => true,
12333
                    CURLOPT_FOLLOWLOCATION => true,
12334
                    CURLOPT_ENCODING => "",
12335
                    CURLOPT_AUTOREFERER => true,
12336
                    CURLOPT_CONNECTTIMEOUT => 120,
12337
                    CURLOPT_TIMEOUT => 120,
12338
                    CURLOPT_MAXREDIRS => 10,
12339
                ];
12340
12341
                $proxySettings = api_get_configuration_value('proxy_settings');
12342
                if (!empty($proxySettings) &&
12343
                    isset($proxySettings['curl_setopt_array'])
12344
                ) {
12345
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12346
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12347
                }
12348
12349
                curl_setopt_array($ch, $options);
12350
                $response = curl_exec($ch);
12351
                $httpCode = curl_getinfo($ch);
12352
                $headers = substr($response, 0, $httpCode['header_size']);
12353
12354
                $error = false;
12355
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12356
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12357
                ) {
12358
                    $error = true;
12359
                }
12360
12361
                if ($error) {
12362
                    Session::write('x_frame_source', $src);
12363
                    $src = 'blank.php?error=x_frames_options';
12364
                }
12365
            }
12366
        }
12367
12368
        return $src;
12369
    }
12370
12371
    /**
12372
     * Check if this LP has a created forum in the basis course.
12373
     *
12374
     * @return bool
12375
     */
12376
    public function lpHasForum()
12377
    {
12378
        $forumTable = Database::get_course_table(TABLE_FORUM);
12379
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12380
12381
        $fakeFrom = "
12382
            $forumTable f
12383
            INNER JOIN $itemProperty ip
12384
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12385
        ";
12386
12387
        $resultData = Database::select(
12388
            'COUNT(f.iid) AS qty',
12389
            $fakeFrom,
12390
            [
12391
                'where' => [
12392
                    'ip.visibility != ? AND ' => 2,
12393
                    'ip.tool = ? AND ' => TOOL_FORUM,
12394
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12395
                    'f.lp_id = ?' => intval($this->lp_id),
12396
                ],
12397
            ],
12398
            'first'
12399
        );
12400
12401
        return $resultData['qty'] > 0;
12402
    }
12403
12404
    /**
12405
     * Get the forum for this learning path.
12406
     *
12407
     * @param int $sessionId
12408
     *
12409
     * @return bool
12410
     */
12411
    public function getForum($sessionId = 0)
12412
    {
12413
        $forumTable = Database::get_course_table(TABLE_FORUM);
12414
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12415
12416
        $fakeFrom = "$forumTable f
12417
            INNER JOIN $itemProperty ip ";
12418
12419
        if ($this->lp_session_id == 0) {
12420
            $fakeFrom .= "
12421
                ON (
12422
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12423
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12424
                    )
12425
                )
12426
            ";
12427
        } else {
12428
            $fakeFrom .= "
12429
                ON (
12430
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12431
                )
12432
            ";
12433
        }
12434
12435
        $resultData = Database::select(
12436
            'f.*',
12437
            $fakeFrom,
12438
            [
12439
                'where' => [
12440
                    'ip.visibility != ? AND ' => 2,
12441
                    'ip.tool = ? AND ' => TOOL_FORUM,
12442
                    'f.session_id = ? AND ' => $sessionId,
12443
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12444
                    'f.lp_id = ?' => intval($this->lp_id),
12445
                ],
12446
            ],
12447
            'first'
12448
        );
12449
12450
        if (empty($resultData)) {
12451
            return false;
12452
        }
12453
12454
        return $resultData;
12455
    }
12456
12457
    /**
12458
     * Create a forum for this learning path.
12459
     *
12460
     * @param int $forumCategoryId
12461
     *
12462
     * @return int The forum ID if was created. Otherwise return false
12463
     */
12464
    public function createForum($forumCategoryId)
12465
    {
12466
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12467
12468
        $forumId = store_forum(
12469
            [
12470
                'lp_id' => $this->lp_id,
12471
                'forum_title' => $this->name,
12472
                'forum_comment' => null,
12473
                'forum_category' => (int) $forumCategoryId,
12474
                'students_can_edit_group' => ['students_can_edit' => 0],
12475
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12476
                'default_view_type_group' => ['default_view_type' => 'flat'],
12477
                'group_forum' => 0,
12478
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12479
            ],
12480
            [],
12481
            true
12482
        );
12483
12484
        return $forumId;
12485
    }
12486
12487
    /**
12488
     * Get the LP Final Item form.
12489
     *
12490
     * @throws Exception
12491
     * @throws HTML_QuickForm_Error
12492
     *
12493
     * @return string
12494
     */
12495
    public function getFinalItemForm()
12496
    {
12497
        $finalItem = $this->getFinalItem();
12498
        $title = '';
12499
12500
        if ($finalItem) {
12501
            $title = $finalItem->get_title();
12502
            $buttonText = get_lang('Save');
12503
            $content = $this->getSavedFinalItem();
12504
        } else {
12505
            $buttonText = get_lang('LPCreateDocument');
12506
            $content = $this->getFinalItemTemplate();
12507
        }
12508
12509
        $courseInfo = api_get_course_info();
12510
        $result = $this->generate_lp_folder($courseInfo);
12511
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12512
        $relative_prefix = '../../';
12513
12514
        $editorConfig = [
12515
            'ToolbarSet' => 'LearningPathDocuments',
12516
            'Width' => '100%',
12517
            'Height' => '500',
12518
            'FullPage' => true,
12519
            'CreateDocumentDir' => $relative_prefix,
12520
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12521
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12522
        ];
12523
12524
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12525
            'type' => 'document',
12526
            'lp_id' => $this->lp_id,
12527
        ]);
12528
12529
        $form = new FormValidator('final_item', 'POST', $url);
12530
        $form->addText('title', get_lang('Title'));
12531
        $form->addButtonSave($buttonText);
12532
        $form->addHtml(
12533
            Display::return_message(
12534
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12535
                'normal',
12536
                false
12537
            )
12538
        );
12539
12540
        $renderer = $form->defaultRenderer();
12541
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12542
12543
        $form->addHtmlEditor(
12544
            'content_lp_certificate',
12545
            null,
12546
            true,
12547
            false,
12548
            $editorConfig,
12549
            true
12550
        );
12551
        $form->addHidden('action', 'add_final_item');
12552
        $form->addHidden('path', Session::read('pathItem'));
12553
        $form->addHidden('previous', $this->get_last());
12554
        $form->setDefaults(
12555
            ['title' => $title, 'content_lp_certificate' => $content]
12556
        );
12557
12558
        if ($form->validate()) {
12559
            $values = $form->exportValues();
12560
            $lastItemId = $this->getLastInFirstLevel();
12561
12562
            if (!$finalItem) {
12563
                $documentId = $this->create_document(
12564
                    $this->course_info,
12565
                    $values['content_lp_certificate'],
12566
                    $values['title']
12567
                );
12568
                $this->add_item(
12569
                    0,
12570
                    $lastItemId,
12571
                    'final_item',
12572
                    $documentId,
12573
                    $values['title'],
12574
                    ''
12575
                );
12576
12577
                Display::addFlash(
12578
                    Display::return_message(get_lang('Added'))
12579
                );
12580
            } else {
12581
                $this->edit_document($this->course_info);
12582
            }
12583
        }
12584
12585
        return $form->returnForm();
12586
    }
12587
12588
    /**
12589
     * Check if the current lp item is first, both, last or none from lp list.
12590
     *
12591
     * @param int $currentItemId
12592
     *
12593
     * @return string
12594
     */
12595
    public function isFirstOrLastItem($currentItemId)
12596
    {
12597
        $lpItemId = [];
12598
        $typeListNotToVerify = self::getChapterTypes();
12599
12600
        // Using get_toc() function instead $this->items because returns the correct order of the items
12601
        foreach ($this->get_toc() as $item) {
12602
            if (!in_array($item['type'], $typeListNotToVerify)) {
12603
                $lpItemId[] = $item['id'];
12604
            }
12605
        }
12606
12607
        $lastLpItemIndex = count($lpItemId) - 1;
12608
        $position = array_search($currentItemId, $lpItemId);
12609
12610
        switch ($position) {
12611
            case 0:
12612
                if (!$lastLpItemIndex) {
12613
                    $answer = 'both';
12614
                    break;
12615
                }
12616
12617
                $answer = 'first';
12618
                break;
12619
            case $lastLpItemIndex:
12620
                $answer = 'last';
12621
                break;
12622
            default:
12623
                $answer = 'none';
12624
        }
12625
12626
        return $answer;
12627
    }
12628
12629
    /**
12630
     * Get whether this is a learning path with the accumulated SCORM time or not.
12631
     *
12632
     * @return int
12633
     */
12634
    public function getAccumulateScormTime()
12635
    {
12636
        return $this->accumulateScormTime;
12637
    }
12638
12639
    /**
12640
     * Set whether this is a learning path with the accumulated SCORM time or not.
12641
     *
12642
     * @param int $value (0 = false, 1 = true)
12643
     *
12644
     * @return bool Always returns true
12645
     */
12646
    public function setAccumulateScormTime($value)
12647
    {
12648
        $this->accumulateScormTime = (int) $value;
12649
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12650
        $lp_id = $this->get_id();
12651
        $sql = "UPDATE $lp_table 
12652
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12653
                WHERE iid = $lp_id";
12654
        Database::query($sql);
12655
12656
        return true;
12657
    }
12658
12659
    /**
12660
     * Returns an HTML-formatted link to a resource, to incorporate directly into
12661
     * the new learning path tool.
12662
     *
12663
     * The function is a big switch on tool type.
12664
     * In each case, we query the corresponding table for information and build the link
12665
     * with that information.
12666
     *
12667
     * @author Yannick Warnier <[email protected]> - rebranding based on
12668
     * previous work (display_addedresource_link_in_learnpath())
12669
     *
12670
     * @param int $course_id      Course code
12671
     * @param int $learningPathId The learning path ID (in lp table)
12672
     * @param int $id_in_path     the unique index in the items table
12673
     * @param int $lpViewId
12674
     *
12675
     * @return string
12676
     */
12677
    public static function rl_get_resource_link_for_learnpath(
12678
        $course_id,
12679
        $learningPathId,
12680
        $id_in_path,
12681
        $lpViewId
12682
    ) {
12683
        $session_id = api_get_session_id();
12684
        $course_info = api_get_course_info_by_id($course_id);
12685
12686
        $learningPathId = (int) $learningPathId;
12687
        $id_in_path = (int) $id_in_path;
12688
        $lpViewId = (int) $lpViewId;
12689
12690
        $em = Database::getManager();
12691
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
12692
12693
        /** @var CLpItem $rowItem */
12694
        $rowItem = $lpItemRepo->findOneBy([
12695
            'cId' => $course_id,
12696
            'lpId' => $learningPathId,
12697
            'iid' => $id_in_path,
12698
        ]);
12699
12700
        if (!$rowItem) {
12701
            // Try one more time with "id"
12702
            /** @var CLpItem $rowItem */
12703
            $rowItem = $lpItemRepo->findOneBy([
12704
                'cId' => $course_id,
12705
                'lpId' => $learningPathId,
12706
                'id' => $id_in_path,
12707
            ]);
12708
12709
            if (!$rowItem) {
12710
                return -1;
12711
            }
12712
        }
12713
12714
        $course_code = $course_info['code'];
12715
        $type = $rowItem->getItemType();
12716
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
12717
        $main_dir_path = api_get_path(WEB_CODE_PATH);
12718
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
12719
        $link = '';
12720
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
12721
12722
        switch ($type) {
12723
            case 'dir':
12724
                return $main_dir_path.'lp/blank.php';
12725
            case TOOL_CALENDAR_EVENT:
12726
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
12727
            case TOOL_ANNOUNCEMENT:
12728
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
12729
            case TOOL_LINK:
12730
                $linkInfo = Link::getLinkInfo($id);
12731
                if (isset($linkInfo['url'])) {
12732
                    return $linkInfo['url'];
12733
                }
12734
12735
                return '';
12736
            case TOOL_QUIZ:
12737
                if (empty($id)) {
12738
                    return '';
12739
                }
12740
12741
                // Get the lp_item_view with the highest view_count.
12742
                $learnpathItemViewResult = $em
12743
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
12744
                    ->findBy(
12745
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
12746
                        ['viewCount' => 'DESC'],
12747
                        1
12748
                    );
12749
                /** @var CLpItemView $learnpathItemViewData */
12750
                $learnpathItemViewData = current($learnpathItemViewResult);
12751
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
12752
12753
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
12754
                    .http_build_query([
12755
                        'lp_init' => 1,
12756
                        'learnpath_item_view_id' => $learnpathItemViewId,
12757
                        'learnpath_id' => $learningPathId,
12758
                        'learnpath_item_id' => $id_in_path,
12759
                        'exerciseId' => $id,
12760
                    ]);
12761
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
12762
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
12763
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
12764
                $myrow = Database::fetch_array($result);
12765
                $path = $myrow['path'];
12766
12767
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
12768
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
12769
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
12770
            case TOOL_FORUM:
12771
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
12772
            case TOOL_THREAD:
12773
                // forum post
12774
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
12775
                if (empty($id)) {
12776
                    return '';
12777
                }
12778
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
12779
                $result = Database::query($sql);
12780
                $myrow = Database::fetch_array($result);
12781
12782
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
12783
                    .$extraParams;
12784
            case TOOL_POST:
12785
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12786
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
12787
                $myrow = Database::fetch_array($result);
12788
12789
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
12790
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
12791
            case TOOL_READOUT_TEXT:
12792
                return api_get_path(WEB_CODE_PATH).
12793
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
12794
            case TOOL_DOCUMENT:
12795
                $document = $em
12796
                    ->getRepository('ChamiloCourseBundle:CDocument')
12797
                    ->findOneBy(['course' => $course_id, 'iid' => $id]);
12798
12799
                if (empty($document)) {
12800
                    // Try with normal id
12801
                    $document = $em
12802
                        ->getRepository('ChamiloCourseBundle:CDocument')
12803
                        ->findOneBy(['course' => $course_id, 'id' => $id]);
12804
12805
                    if (empty($document)) {
12806
                        return '';
12807
                    }
12808
                }
12809
12810
                $documentPathInfo = pathinfo($document->getPath());
12811
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'flv', 'm4v'];
12812
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
12813
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
12814
12815
                $openmethod = 2;
12816
                $officedoc = false;
12817
                Session::write('openmethod', $openmethod);
12818
                Session::write('officedoc', $officedoc);
12819
12820
                if ($showDirectUrl) {
12821
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
12822
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
12823
                        if (Link::isPdfLink($file)) {
12824
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
12825
12826
                            return $pdfUrl;
12827
                        }
12828
                    }
12829
12830
                    return $file;
12831
                }
12832
12833
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
12834
            case TOOL_LP_FINAL_ITEM:
12835
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
12836
                    .$extraParams;
12837
            case 'assignments':
12838
                return $main_dir_path.'work/work.php?'.$extraParams;
12839
            case TOOL_DROPBOX:
12840
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
12841
            case 'introduction_text': //DEPRECATED
12842
                return '';
12843
            case TOOL_COURSE_DESCRIPTION:
12844
                return $main_dir_path.'course_description?'.$extraParams;
12845
            case TOOL_GROUP:
12846
                return $main_dir_path.'group/group.php?'.$extraParams;
12847
            case TOOL_USER:
12848
                return $main_dir_path.'user/user.php?'.$extraParams;
12849
            case TOOL_STUDENTPUBLICATION:
12850
                if (!empty($rowItem->getPath())) {
12851
                    return $main_dir_path.'work/work_list.php?id='.$rowItem->getPath().'&'.$extraParams;
12852
                }
12853
12854
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
12855
        }
12856
12857
        return $link;
12858
    }
12859
12860
    /**
12861
     * Gets the name of a resource (generally used in learnpath when no name is provided).
12862
     *
12863
     * @author Yannick Warnier <[email protected]>
12864
     *
12865
     * @param string $course_code    Course code
12866
     * @param int    $learningPathId
12867
     * @param int    $id_in_path     The resource ID
12868
     *
12869
     * @return string
12870
     */
12871
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
12872
    {
12873
        $_course = api_get_course_info($course_code);
12874
        if (empty($_course)) {
12875
            return '';
12876
        }
12877
        $course_id = $_course['real_id'];
12878
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12879
        $learningPathId = (int) $learningPathId;
12880
        $id_in_path = (int) $id_in_path;
12881
12882
        $sql = "SELECT item_type, title, ref 
12883
                FROM $tbl_lp_item
12884
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
12885
        $res_item = Database::query($sql);
12886
12887
        if (Database::num_rows($res_item) < 1) {
12888
            return '';
12889
        }
12890
        $row_item = Database::fetch_array($res_item);
12891
        $type = strtolower($row_item['item_type']);
12892
        $id = $row_item['ref'];
12893
        $output = '';
12894
12895
        switch ($type) {
12896
            case TOOL_CALENDAR_EVENT:
12897
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
12898
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
12899
                $myrow = Database::fetch_array($result);
12900
                $output = $myrow['title'];
12901
                break;
12902
            case TOOL_ANNOUNCEMENT:
12903
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
12904
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
12905
                $myrow = Database::fetch_array($result);
12906
                $output = $myrow['title'];
12907
                break;
12908
            case TOOL_LINK:
12909
                // Doesn't take $target into account.
12910
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
12911
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
12912
                $myrow = Database::fetch_array($result);
12913
                $output = $myrow['title'];
12914
                break;
12915
            case TOOL_QUIZ:
12916
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
12917
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
12918
                $myrow = Database::fetch_array($result);
12919
                $output = $myrow['title'];
12920
                break;
12921
            case TOOL_FORUM:
12922
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
12923
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
12924
                $myrow = Database::fetch_array($result);
12925
                $output = $myrow['forum_name'];
12926
                break;
12927
            case TOOL_THREAD:
12928
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12929
                // Grabbing the title of the post.
12930
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
12931
                $result_title = Database::query($sql_title);
12932
                $myrow_title = Database::fetch_array($result_title);
12933
                $output = $myrow_title['post_title'];
12934
                break;
12935
            case TOOL_POST:
12936
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
12937
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
12938
                $result = Database::query($sql);
12939
                $post = Database::fetch_array($result);
12940
                $output = $post['post_title'];
12941
                break;
12942
            case 'dir':
12943
            case TOOL_DOCUMENT:
12944
                $title = $row_item['title'];
12945
                $output = '-';
12946
                if (!empty($title)) {
12947
                    $output = $title;
12948
                }
12949
                break;
12950
            case 'hotpotatoes':
12951
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
12952
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
12953
                $myrow = Database::fetch_array($result);
12954
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
12955
                $last = count($pathname) - 1; // Making a correct name for the link.
12956
                $filename = $pathname[$last]; // Making a correct name for the link.
12957
                $myrow['path'] = rawurlencode($myrow['path']);
12958
                $output = $filename;
12959
                break;
12960
        }
12961
12962
        return stripslashes($output);
12963
    }
12964
12965
    /**
12966
     * Get the parent names for the current item.
12967
     *
12968
     * @param int $newItemId Optional. The item ID
12969
     *
12970
     * @return array
12971
     */
12972
    public function getCurrentItemParentNames($newItemId = 0)
12973
    {
12974
        $newItemId = $newItemId ?: $this->get_current_item_id();
12975
        $return = [];
12976
        $item = $this->getItem($newItemId);
12977
        $parent = $this->getItem($item->get_parent());
12978
12979
        while ($parent) {
12980
            $return[] = $parent->get_title();
12981
            $parent = $this->getItem($parent->get_parent());
12982
        }
12983
12984
        return array_reverse($return);
12985
    }
12986
12987
    /**
12988
     * Reads and process "lp_subscription_settings" setting.
12989
     *
12990
     * @return array
12991
     */
12992
    public static function getSubscriptionSettings()
12993
    {
12994
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
12995
        if (empty($subscriptionSettings)) {
12996
            // By default allow both settings
12997
            $subscriptionSettings = [
12998
                'allow_add_users_to_lp' => true,
12999
                'allow_add_users_to_lp_category' => true,
13000
            ];
13001
        } else {
13002
            $subscriptionSettings = $subscriptionSettings['options'];
13003
        }
13004
13005
        return $subscriptionSettings;
13006
    }
13007
13008
    /**
13009
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13010
     */
13011
    public function exportToCourseBuildFormat()
13012
    {
13013
        if (!api_is_allowed_to_edit()) {
13014
            return false;
13015
        }
13016
13017
        $courseBuilder = new CourseBuilder();
13018
        $itemList = [];
13019
        /** @var learnpathItem $item */
13020
        foreach ($this->items as $item) {
13021
            $itemList[$item->get_type()][] = $item->get_path();
13022
        }
13023
13024
        if (empty($itemList)) {
13025
            return false;
13026
        }
13027
13028
        if (isset($itemList['document'])) {
13029
            // Get parents
13030
            foreach ($itemList['document'] as $documentId) {
13031
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13032
                if (!empty($documentInfo['parents'])) {
13033
                    foreach ($documentInfo['parents'] as $parentInfo) {
13034
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13035
                            continue;
13036
                        }
13037
                        $itemList['document'][] = $parentInfo['iid'];
13038
                    }
13039
                }
13040
            }
13041
13042
            $courseInfo = api_get_course_info();
13043
            foreach ($itemList['document'] as $documentId) {
13044
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13045
                $items = DocumentManager::get_resources_from_source_html(
13046
                    $documentInfo['absolute_path'],
13047
                    true,
13048
                    TOOL_DOCUMENT
13049
                );
13050
13051
                if (!empty($items)) {
13052
                    foreach ($items as $item) {
13053
                        // Get information about source url
13054
                        $url = $item[0]; // url
13055
                        $scope = $item[1]; // scope (local, remote)
13056
                        $type = $item[2]; // type (rel, abs, url)
13057
13058
                        $origParseUrl = parse_url($url);
13059
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13060
13061
                        if ($scope == 'local') {
13062
                            if ($type == 'abs' || $type == 'rel') {
13063
                                $documentFile = strstr($realOrigPath, 'document');
13064
                                if (strpos($realOrigPath, $documentFile) !== false) {
13065
                                    $documentFile = str_replace('document', '', $documentFile);
13066
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13067
                                    // Document found! Add it to the list
13068
                                    if ($itemDocumentId) {
13069
                                        $itemList['document'][] = $itemDocumentId;
13070
                                    }
13071
                                }
13072
                            }
13073
                        }
13074
                    }
13075
                }
13076
            }
13077
13078
            $courseBuilder->build_documents(
13079
                api_get_session_id(),
13080
                $this->get_course_int_id(),
13081
                true,
13082
                $itemList['document']
13083
            );
13084
        }
13085
13086
        if (isset($itemList['quiz'])) {
13087
            $courseBuilder->build_quizzes(
13088
                api_get_session_id(),
13089
                $this->get_course_int_id(),
13090
                true,
13091
                $itemList['quiz']
13092
            );
13093
        }
13094
13095
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13096
13097
        /*if (!empty($itemList['thread'])) {
13098
            $postList = [];
13099
            foreach ($itemList['thread'] as $postId) {
13100
                $post = get_post_information($postId);
13101
                if ($post) {
13102
                    if (!isset($itemList['forum'])) {
13103
                        $itemList['forum'] = [];
13104
                    }
13105
                    $itemList['forum'][] = $post['forum_id'];
13106
                    $postList[] = $postId;
13107
                }
13108
            }
13109
13110
            if (!empty($postList)) {
13111
                $courseBuilder->build_forum_posts(
13112
                    $this->get_course_int_id(),
13113
                    null,
13114
                    null,
13115
                    $postList
13116
                );
13117
            }
13118
        }*/
13119
13120
        if (!empty($itemList['thread'])) {
13121
            $threadList = [];
13122
            $em = Database::getManager();
13123
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13124
            foreach ($itemList['thread'] as $threadId) {
13125
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13126
                $thread = $repo->find($threadId);
13127
                if ($thread) {
13128
                    $itemList['forum'][] = $thread->getForum() ? $thread->getForum()->getIid() : 0;
13129
                    $threadList[] = $thread->getIid();
13130
                }
13131
            }
13132
13133
            if (!empty($threadList)) {
13134
                $courseBuilder->build_forum_topics(
13135
                    api_get_session_id(),
13136
                    $this->get_course_int_id(),
13137
                    null,
13138
                    $threadList
13139
                );
13140
            }
13141
        }
13142
13143
        $forumCategoryList = [];
13144
        if (isset($itemList['forum'])) {
13145
            foreach ($itemList['forum'] as $forumId) {
13146
                $forumInfo = get_forums($forumId);
13147
                $forumCategoryList[] = $forumInfo['forum_category'];
13148
            }
13149
        }
13150
13151
        if (!empty($forumCategoryList)) {
13152
            $courseBuilder->build_forum_category(
13153
                api_get_session_id(),
13154
                $this->get_course_int_id(),
13155
                true,
13156
                $forumCategoryList
13157
            );
13158
        }
13159
13160
        if (!empty($itemList['forum'])) {
13161
            $courseBuilder->build_forums(
13162
                api_get_session_id(),
13163
                $this->get_course_int_id(),
13164
                true,
13165
                $itemList['forum']
13166
            );
13167
        }
13168
13169
        if (isset($itemList['link'])) {
13170
            $courseBuilder->build_links(
13171
                api_get_session_id(),
13172
                $this->get_course_int_id(),
13173
                true,
13174
                $itemList['link']
13175
            );
13176
        }
13177
13178
        if (!empty($itemList['student_publication'])) {
13179
            $courseBuilder->build_works(
13180
                api_get_session_id(),
13181
                $this->get_course_int_id(),
13182
                true,
13183
                $itemList['student_publication']
13184
            );
13185
        }
13186
13187
        $courseBuilder->build_learnpaths(
13188
            api_get_session_id(),
13189
            $this->get_course_int_id(),
13190
            true,
13191
            [$this->get_id()],
13192
            false
13193
        );
13194
13195
        $courseBuilder->restoreDocumentsFromList();
13196
13197
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13198
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13199
        $result = DocumentManager::file_send_for_download(
13200
            $zipPath,
13201
            true,
13202
            $this->get_name().'.zip'
13203
        );
13204
13205
        if ($result) {
13206
            api_not_allowed();
13207
        }
13208
13209
        return true;
13210
    }
13211
13212
    /**
13213
     * Get whether this is a learning path with the accumulated work time or not.
13214
     *
13215
     * @return int
13216
     */
13217
    public function getAccumulateWorkTime()
13218
    {
13219
        return (int) $this->accumulateWorkTime;
13220
    }
13221
13222
    /**
13223
     * Get whether this is a learning path with the accumulated work time or not.
13224
     *
13225
     * @return int
13226
     */
13227
    public function getAccumulateWorkTimeTotalCourse()
13228
    {
13229
        $table = Database::get_course_table(TABLE_LP_MAIN);
13230
        $sql = "SELECT SUM(accumulate_work_time) AS total 
13231
                FROM $table 
13232
                WHERE c_id = ".$this->course_int_id;
13233
        $result = Database::query($sql);
13234
        $row = Database::fetch_array($result);
13235
13236
        return (int) $row['total'];
13237
    }
13238
13239
    /**
13240
     * Set whether this is a learning path with the accumulated work time or not.
13241
     *
13242
     * @param int $value (0 = false, 1 = true)
13243
     *
13244
     * @return bool
13245
     */
13246
    public function setAccumulateWorkTime($value)
13247
    {
13248
        if (!api_get_configuration_value('lp_minimum_time')) {
13249
            return false;
13250
        }
13251
13252
        $this->accumulateWorkTime = (int) $value;
13253
        $table = Database::get_course_table(TABLE_LP_MAIN);
13254
        $lp_id = $this->get_id();
13255
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13256
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13257
        Database::query($sql);
13258
13259
        return true;
13260
    }
13261
13262
    /**
13263
     * @param int $lpId
13264
     * @param int $courseId
13265
     *
13266
     * @return mixed
13267
     */
13268
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13269
    {
13270
        $lpId = (int) $lpId;
13271
        $courseId = (int) $courseId;
13272
13273
        $table = Database::get_course_table(TABLE_LP_MAIN);
13274
        $sql = "SELECT accumulate_work_time 
13275
                FROM $table 
13276
                WHERE c_id = $courseId AND id = $lpId";
13277
        $result = Database::query($sql);
13278
        $row = Database::fetch_array($result);
13279
13280
        return $row['accumulate_work_time'];
13281
    }
13282
13283
    /**
13284
     * @param int $courseId
13285
     *
13286
     * @return int
13287
     */
13288
    public static function getAccumulateWorkTimeTotal($courseId)
13289
    {
13290
        $table = Database::get_course_table(TABLE_LP_MAIN);
13291
        $courseId = (int) $courseId;
13292
        $sql = "SELECT SUM(accumulate_work_time) AS total
13293
                FROM $table
13294
                WHERE c_id = $courseId";
13295
        $result = Database::query($sql);
13296
        $row = Database::fetch_array($result);
13297
13298
        return (int) $row['total'];
13299
    }
13300
13301
    /**
13302
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13303
     * and put the images in.
13304
     *
13305
     * @return array
13306
     */
13307
    public static function getIconSelect()
13308
    {
13309
        $theme = api_get_visual_theme();
13310
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13311
        $icons = ['' => get_lang('SelectAnOption')];
13312
13313
        if (is_dir($path)) {
13314
            $finder = new Finder();
13315
            $finder->files()->in($path);
13316
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13317
            /** @var SplFileInfo $file */
13318
            foreach ($finder as $file) {
13319
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13320
                    $icons[$file->getFilename()] = $file->getFilename();
13321
                }
13322
            }
13323
        }
13324
13325
        return $icons;
13326
    }
13327
13328
    /**
13329
     * @param int $lpId
13330
     *
13331
     * @return string
13332
     */
13333
    public static function getSelectedIcon($lpId)
13334
    {
13335
        $extraFieldValue = new ExtraFieldValue('lp');
13336
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13337
        $icon = '';
13338
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13339
            $icon = $lpIcon['value'];
13340
        }
13341
13342
        return $icon;
13343
    }
13344
13345
    /**
13346
     * @param int $lpId
13347
     *
13348
     * @return string
13349
     */
13350
    public static function getSelectedIconHtml($lpId)
13351
    {
13352
        $icon = self::getSelectedIcon($lpId);
13353
13354
        if (empty($icon)) {
13355
            return '';
13356
        }
13357
13358
        $theme = api_get_visual_theme();
13359
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13360
13361
        return Display::img($path);
13362
    }
13363
13364
    /**
13365
     * @param string $value
13366
     *
13367
     * @return string
13368
     */
13369
    public function cleanItemTitle($value)
13370
    {
13371
        $value = Security::remove_XSS(strip_tags($value));
13372
13373
        return $value;
13374
    }
13375
13376
    /**
13377
     * @param FormValidator $form
13378
     */
13379
    public function setItemTitle(FormValidator $form)
13380
    {
13381
        if (api_get_configuration_value('save_titles_as_html')) {
13382
            $form->addHtmlEditor(
13383
                'title',
13384
                get_lang('Title'),
13385
                true,
13386
                false,
13387
                ['ToolbarSet' => 'TitleAsHtml']
13388
            );
13389
        } else {
13390
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13391
            $form->applyFilter('title', 'trim');
13392
            $form->applyFilter('title', 'html_filter');
13393
        }
13394
    }
13395
13396
    /**
13397
     * @return array
13398
     */
13399
    public function getItemsForForm($addParentCondition = false)
13400
    {
13401
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13402
        $course_id = api_get_course_int_id();
13403
13404
        $sql = "SELECT * FROM $tbl_lp_item 
13405
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13406
13407
        if ($addParentCondition) {
13408
            $sql .= ' AND parent_item_id = 0 ';
13409
        }
13410
        $sql .= ' ORDER BY display_order ASC';
13411
13412
        $result = Database::query($sql);
13413
        $arrLP = [];
13414
        while ($row = Database::fetch_array($result)) {
13415
            $arrLP[] = [
13416
                'iid' => $row['iid'],
13417
                'id' => $row['iid'],
13418
                'item_type' => $row['item_type'],
13419
                'title' => $this->cleanItemTitle($row['title']),
13420
                'title_raw' => $row['title'],
13421
                'path' => $row['path'],
13422
                'description' => Security::remove_XSS($row['description']),
13423
                'parent_item_id' => $row['parent_item_id'],
13424
                'previous_item_id' => $row['previous_item_id'],
13425
                'next_item_id' => $row['next_item_id'],
13426
                'display_order' => $row['display_order'],
13427
                'max_score' => $row['max_score'],
13428
                'min_score' => $row['min_score'],
13429
                'mastery_score' => $row['mastery_score'],
13430
                'prerequisite' => $row['prerequisite'],
13431
                'max_time_allowed' => $row['max_time_allowed'],
13432
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13433
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13434
            ];
13435
        }
13436
13437
        return $arrLP;
13438
    }
13439
13440
    /**
13441
     * Get the depth level of LP item.
13442
     *
13443
     * @param array $items
13444
     * @param int   $currentItemId
13445
     *
13446
     * @return int
13447
     */
13448
    private static function get_level_for_item($items, $currentItemId)
13449
    {
13450
        $parentItemId = 0;
13451
        if (isset($items[$currentItemId])) {
13452
            $parentItemId = $items[$currentItemId]->parent;
13453
        }
13454
13455
        if ($parentItemId == 0) {
13456
            return 0;
13457
        } else {
13458
            return self::get_level_for_item($items, $parentItemId) + 1;
13459
        }
13460
    }
13461
13462
    /**
13463
     * Generate the link for a learnpath category as course tool.
13464
     *
13465
     * @param int $categoryId
13466
     *
13467
     * @return string
13468
     */
13469
    private static function getCategoryLinkForTool($categoryId)
13470
    {
13471
        $categoryId = (int) $categoryId;
13472
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13473
            .http_build_query(
13474
                [
13475
                    'action' => 'view_category',
13476
                    'id' => $categoryId,
13477
                ]
13478
            );
13479
13480
        return $link;
13481
    }
13482
13483
    /**
13484
     * Return the scorm item type object with spaces replaced with _
13485
     * The return result is use to build a css classname like scorm_type_$return.
13486
     *
13487
     * @param $in_type
13488
     *
13489
     * @return mixed
13490
     */
13491
    private static function format_scorm_type_item($in_type)
13492
    {
13493
        return str_replace(' ', '_', $in_type);
13494
    }
13495
13496
    /**
13497
     * Check and obtain the lp final item if exist.
13498
     *
13499
     * @return learnpathItem
13500
     */
13501
    private function getFinalItem()
13502
    {
13503
        if (empty($this->items)) {
13504
            return null;
13505
        }
13506
13507
        foreach ($this->items as $item) {
13508
            if ($item->type !== 'final_item') {
13509
                continue;
13510
            }
13511
13512
            return $item;
13513
        }
13514
    }
13515
13516
    /**
13517
     * Get the LP Final Item Template.
13518
     *
13519
     * @return string
13520
     */
13521
    private function getFinalItemTemplate()
13522
    {
13523
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13524
    }
13525
13526
    /**
13527
     * Get the LP Final Item Url.
13528
     *
13529
     * @return string
13530
     */
13531
    private function getSavedFinalItem()
13532
    {
13533
        $finalItem = $this->getFinalItem();
13534
        $doc = DocumentManager::get_document_data_by_id(
13535
            $finalItem->path,
13536
            $this->cc
13537
        );
13538
        if ($doc && file_exists($doc['absolute_path'])) {
13539
            return file_get_contents($doc['absolute_path']);
13540
        }
13541
13542
        return '';
13543
    }
13544
}
13545