Completed
Push — master ( 88715c...d7c14e )
by Julito
09:34
created

learnpath   F

Complexity

Total Complexity 1938

Size/Duplication

Total Lines 14054
Duplicated Lines 0 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
eloc 7877
dl 0
loc 14054
rs 0.8
c 4
b 1
f 0
wmc 1938

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8151
            if ($action != 'add') {
8152
                if ($arrLP[$i]['item_type'] == 'dir' &&
8153
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8154
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8155
                ) {
8156
                    $selectParent->addOption(
8157
                        $arrLP[$i]['title'],
8158
                        $arrLP[$i]['id'],
8159
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8160
                    );
8161
8162
                    if ($parent == $arrLP[$i]['id']) {
8163
                        $selectParent->setSelected($arrLP[$i]['id']);
8164
                    }
8165
                } else {
8166
                    $arrHide[] = $arrLP[$i]['id'];
8167
                }
8168
            } else {
8169
                if ($arrLP[$i]['item_type'] == 'dir') {
8170
                    $selectParent->addOption(
8171
                        $arrLP[$i]['title'],
8172
                        $arrLP[$i]['id'],
8173
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8174
                    );
8175
8176
                    if ($parent == $arrLP[$i]['id']) {
8177
                        $selectParent->setSelected($arrLP[$i]['id']);
8178
                    }
8179
                }
8180
            }
8181
        }
8182
8183
        if (is_array($arrLP)) {
8184
            reset($arrLP);
8185
        }
8186
8187
        $selectPrevious = $form->addSelect(
8188
            'previous',
8189
            get_lang('Position'),
8190
            [],
8191
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8192
        );
8193
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8194
8195
        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...
8196
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8197
                $arrLP[$i]['id'] != $id
8198
            ) {
8199
                $selectPrevious->addOption(
8200
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8201
                    $arrLP[$i]['id']
8202
                );
8203
8204
                if (isset($extra_info['previous_item_id']) &&
8205
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8206
                ) {
8207
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8208
                } elseif ($action == 'add') {
8209
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8210
                }
8211
            }
8212
        }
8213
8214
        if ($action != 'move') {
8215
            $id_prerequisite = 0;
8216
            if (is_array($arrLP)) {
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
            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...
8227
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8228
                    if (isset($extra_info['previous_item_id']) &&
8229
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8230
                    ) {
8231
                        $s_selected_position = $arrLP[$i]['id'];
8232
                    } elseif ($action == 'add') {
8233
                        $s_selected_position = 0;
8234
                    }
8235
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8236
                }
8237
            }
8238
        }
8239
8240
        if ($action == 'add') {
8241
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8242
        } else {
8243
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8244
        }
8245
8246
        if ($action == 'move') {
8247
            $form->addHidden('title', $item_title);
8248
            $form->addHidden('description', $item_description);
8249
        }
8250
8251
        if (is_numeric($extra_info)) {
8252
            $form->addHidden('path', $extra_info);
8253
        } elseif (is_array($extra_info)) {
8254
            $form->addHidden('path', $extra_info['path']);
8255
        }
8256
        $form->addHidden('type', TOOL_FORUM);
8257
        $form->addHidden('post_time', time());
8258
        $form->setDefaults($defaults);
8259
8260
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8261
    }
8262
8263
    /**
8264
     * Return HTML form to add/edit forum threads.
8265
     *
8266
     * @param string $action
8267
     * @param int    $id         Item ID if already exists in learning path
8268
     * @param string $extra_info
8269
     *
8270
     * @throws Exception
8271
     *
8272
     * @return string HTML form
8273
     */
8274
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8275
    {
8276
        $course_id = api_get_course_int_id();
8277
        if (empty($course_id)) {
8278
            return null;
8279
        }
8280
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8281
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8282
8283
        if ($id != 0 && is_array($extra_info)) {
8284
            $item_title = stripslashes($extra_info['title']);
8285
        } elseif (is_numeric($extra_info)) {
8286
            $sql = "SELECT thread_title as title FROM $tbl_forum
8287
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8288
8289
            $result = Database::query($sql);
8290
            $row = Database::fetch_array($result);
8291
8292
            $item_title = $row['title'];
8293
            $item_description = '';
8294
        } else {
8295
            $item_title = '';
8296
            $item_description = '';
8297
        }
8298
8299
        if ($id != 0 && is_array($extra_info)) {
8300
            $parent = $extra_info['parent_item_id'];
8301
        } else {
8302
            $parent = 0;
8303
        }
8304
8305
        $sql = "SELECT * FROM $tbl_lp_item
8306
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
8307
        $result = Database::query($sql);
8308
8309
        $arrLP = [];
8310
        while ($row = Database::fetch_array($result)) {
8311
            $arrLP[] = [
8312
                'id' => $row['iid'],
8313
                'item_type' => $row['item_type'],
8314
                'title' => $row['title'],
8315
                'path' => $row['path'],
8316
                'description' => $row['description'],
8317
                'parent_item_id' => $row['parent_item_id'],
8318
                'previous_item_id' => $row['previous_item_id'],
8319
                'next_item_id' => $row['next_item_id'],
8320
                'display_order' => $row['display_order'],
8321
                'max_score' => $row['max_score'],
8322
                'min_score' => $row['min_score'],
8323
                'mastery_score' => $row['mastery_score'],
8324
                'prerequisite' => $row['prerequisite'],
8325
            ];
8326
        }
8327
8328
        $this->tree_array($arrLP);
8329
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8330
        unset($this->arrMenu);
8331
8332
        $form = new FormValidator(
8333
            'thread_form',
8334
            'POST',
8335
            $this->getCurrentBuildingModeURL()
8336
        );
8337
        $defaults = [];
8338
8339
        if ($action == 'add') {
8340
            $legend = get_lang('CreateTheForum');
8341
        } elseif ($action == 'move') {
8342
            $legend = get_lang('MoveTheCurrentForum');
8343
        } else {
8344
            $legend = get_lang('EditCurrentForum');
8345
        }
8346
8347
        $form->addHeader($legend);
8348
        $selectParent = $form->addSelect(
8349
            'parent',
8350
            get_lang('Parent'),
8351
            [],
8352
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8353
        );
8354
        $selectParent->addOption($this->name, 0);
8355
8356
        $arrHide = [
8357
            $id,
8358
        ];
8359
8360
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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