Passed
Push — 1.11.x ( 4f5e73...f58d83 )
by Julito
13:00
created

learnpath   F

Complexity

Total Complexity 1921

Size/Duplication

Total Lines 14119
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 1
Metric Value
eloc 7823
c 7
b 0
f 1
dl 0
loc 14119
rs 0.8
wmc 1921

226 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7723
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7724
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7725
                }
7726
            }
7727
        }
7728
7729
        if ('edit' === $action) {
7730
            $extraField = new ExtraField('lp_item');
7731
            $extraField->addElements($form, $id);
7732
        }
7733
7734
        if ($action === 'add') {
7735
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7736
        } else {
7737
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7738
        }
7739
7740
        if ($action === 'move') {
7741
            $form->addHidden('title', $item_title);
7742
            $form->addHidden('description', $item_description);
7743
        }
7744
7745
        if (is_numeric($extra_info)) {
7746
            $form->addHidden('path', $extra_info);
7747
        } elseif (is_array($extra_info)) {
7748
            $form->addHidden('path', $extra_info['path']);
7749
        }
7750
7751
        $form->addHidden('type', TOOL_QUIZ);
7752
        $form->addHidden('post_time', time());
7753
        $form->setDefaults($defaults);
7754
7755
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7756
    }
7757
7758
    /**
7759
     * Addition of Hotpotatoes tests.
7760
     *
7761
     * @param string $action
7762
     * @param int    $id         Internal ID of the item
7763
     * @param string $extra_info
7764
     *
7765
     * @return string HTML structure to display the hotpotatoes addition formular
7766
     */
7767
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7768
    {
7769
        $course_id = api_get_course_int_id();
7770
        $uploadPath = DIR_HOTPOTATOES;
7771
7772
        if ($id != 0 && is_array($extra_info)) {
7773
            $item_title = stripslashes($extra_info['title']);
7774
            $item_description = stripslashes($extra_info['description']);
7775
        } elseif (is_numeric($extra_info)) {
7776
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7777
7778
            $sql = "SELECT * FROM $TBL_DOCUMENT
7779
                    WHERE
7780
                        c_id = $course_id AND
7781
                        path LIKE '".$uploadPath."/%/%htm%' AND
7782
                        iid = ".(int) $extra_info."
7783
                    ORDER BY iid ASC";
7784
7785
            $res_hot = Database::query($sql);
7786
            $row = Database::fetch_array($res_hot);
7787
7788
            $item_title = $row['title'];
7789
            $item_description = $row['description'];
7790
7791
            if (!empty($row['comment'])) {
7792
                $item_title = $row['comment'];
7793
            }
7794
        } else {
7795
            $item_title = '';
7796
            $item_description = '';
7797
        }
7798
7799
        $parent = 0;
7800
        if ($id != 0 && is_array($extra_info)) {
7801
            $parent = $extra_info['parent_item_id'];
7802
        }
7803
7804
        $arrLP = $this->getItemsForForm();
7805
        $legend = '<legend>';
7806
        if ($action == 'add') {
7807
            $legend .= get_lang('CreateTheExercise');
7808
        } elseif ($action == 'move') {
7809
            $legend .= get_lang('MoveTheCurrentExercise');
7810
        } else {
7811
            $legend .= get_lang('EditCurrentExecice');
7812
        }
7813
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7814
            $legend .= Display:: return_message(
7815
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7816
            );
7817
        }
7818
        $legend .= '</legend>';
7819
7820
        $return = '<form method="POST">';
7821
        $return .= $legend;
7822
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7823
        $return .= '<tr>';
7824
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7825
        $return .= '<td class="input">';
7826
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7827
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7828
        $arrHide = [$id];
7829
7830
        if (count($arrLP) > 0) {
7831
            for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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