Passed
Push — master ( 447a4c...2f567c )
by Julito
09:40
created

learnpath   F

Complexity

Total Complexity 1871

Size/Duplication

Total Lines 13884
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7768
c 5
b 1
f 0
dl 0
loc 13884
rs 0.8
wmc 1871

215 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
A getCourseCode() 0 3 1
A getSavedFinalItem() 0 12 3
A format_scorm_type_item() 0 3 1
A getCategoryLinkForTool() 0 12 1
A getFinalItem() 0 12 4
A getFinalItemTemplate() 0 3 1
A get_level_for_item() 0 11 3
B get_preview_image_path() 0 28 7
F __construct() 0 337 55
F add_item() 0 220 17
A close() 0 16 3
F autocomplete_parents() 0 105 18
B delete_item() 0 78 9
F add_lp() 0 147 14
F delete() 0 111 18
A delete_children_items() 0 23 5
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 216 42
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 341 61
A switch_attempt_mode() 0 21 5
A getAccumulateScormTime() 0 3 1
F create_document() 0 158 30
D prerequisites_match() 0 67 16
A previous() 0 11 1
A tree_array() 0 4 1
A createCategory() 0 18 1
B move_down() 0 54 8
F get_exercises() 0 162 13
C fixBlockedLinks() 0 64 11
A open() 0 12 2
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 191 34
F display_document_form() 0 375 73
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
F createReadOutText() 0 121 27
A moveDownCategory() 0 11 2
A get_type_static() 0 16 3
F displayFrmReadOutText() 0 304 60
A sort_tree_array() 0 12 3
F scormExport() 0 973 114
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
F display_item_form() 0 232 39
A select_previous_item_id() 0 22 1
B get_previous_index() 0 24 7
A set_seriousgame_mode() 0 23 4
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
A returnLpItemList() 0 15 2
D get_progress_bar_text() 0 43 11
A get_teacher_toc_buttons() 0 21 4
A get_js_lib() 0 8 2
F first() 0 71 20
A getItem() 0 7 3
D get_package_type() 0 79 19
A getCategoryId() 0 3 1
B create_tree_array() 0 37 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
A getChapterTypes() 0 4 1
C rl_get_resource_name() 0 100 14
B restart() 0 40 6
A set_author() 0 10 1
B get_iv_interactions_array() 0 54 8
A getCurrentItemParentNames() 0 14 3
A getSubscribeUsers() 0 3 1
A set_attempt_mode() 0 32 5
A deleteCategory() 0 37 4
A update_display_order() 0 29 5
B generate_lp_folder() 0 48 6
A getAccumulateWorkTimeTotalCourse() 0 10 1
F display_quiz_form() 0 193 36
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
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
F display_item_prerequisites_form() 0 188 19
B print_recursive() 0 40 10
A clear_prerequisites() 0 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 180 37
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 45 8
B get_js_info() 0 44 6
A get_id() 0 7 2
B get_js_dropdown_array() 0 74 6
A getFinalEvaluationItem() 0 12 3
B build_action_menu() 0 126 5
A get_next_item_id() 0 20 6
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 getSubscriptionSettings() 0 14 2
B verify_document_size() 0 22 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
C get_forums() 0 111 11
F edit_document() 0 82 17
F move_item() 0 153 27
A getTotalItemsCountWithoutDirs() 0 11 3
A getCurrentAttempt() 0 10 2
A delete_lp_image() 0 17 5
F display_forum_form() 0 205 40
A update_scorm_debug() 0 23 4
A toggleCategoryVisibility() 0 29 3
C getParentToc() 0 64 13
A setAccumulateScormTime() 0 11 1
A update_reinit() 0 23 4
D getListArrayToc() 0 76 11
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 181 34
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 18 3
A getCategories() 0 12 1
B get_next_index() 0 32 10
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 35 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 24 4
C categoryIsVisibleForStudent() 0 73 15
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 175 30
A setAccumulateWorkTime() 0 14 2
B save_item() 0 47 9
A set_name() 0 29 3
F processBuildMenuElements() 0 428 52

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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