Passed
Push — master ( c6e3ba...378572 )
by Julito
13:32
created

learnpath   F

Complexity

Total Complexity 1824

Size/Duplication

Total Lines 13734
Duplicated Lines 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
eloc 7670
dl 0
loc 13734
rs 0.8
c 5
b 1
f 0
wmc 1824

213 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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