Passed
Push — 1.11.x ( e09374...206f1a )
by Julito
12:56
created

learnpath   F

Complexity

Total Complexity 1927

Size/Duplication

Total Lines 14175
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 1
Metric Value
eloc 7857
dl 0
loc 14175
rs 0.8
c 8
b 0
f 1
wmc 1927

227 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
A set_autolaunch() 0 27 2
A copy() 0 26 1
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A display_lp_prerequisites_list() 0 31 5
F scormExport() 0 973 114
A select_previous_item_id() 0 22 1
A create_path() 0 14 5
B upload_image() 0 40 6
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B verify_document_size() 0 22 8
B get_documents() 0 99 2
C get_forums() 0 111 11
A delete_lp_image() 0 17 5
A get_student_publications() 0 41 3
F autocomplete_parents() 0 101 17
A get_common_index_terms_by_prefix() 0 17 3
F createReadOutText() 0 135 27
A getCategory() 0 6 1
A createCategory() 0 27 3
F get_exercises() 0 162 13
A getLpList() 0 27 3
A getCategorySessionId() 0 18 3
A updateCategory() 0 9 2
A getCountCategories() 0 10 2
A moveDownCategory() 0 9 2
B get_links() 0 109 6
A moveUpCategory() 0 9 2
F display_item_prerequisites_form() 0 183 21
B display_move_item() 0 52 11
B get_js_dropdown_array() 0 70 6
A getCategoryByCourse() 0 5 1
B display_manipulate() 0 97 9
A getCategories() 0 9 1
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 create_document() 0 169 30
B generate_lp_folder() 0 57 8
C display_item() 0 98 16
F edit_document() 0 82 17
A generate_learning_path_folder() 0 28 4
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 switch_attempt_mode() 0 16 4
A tree_array() 0 4 1
A set_jslib() 0 15 2
A update_default_scorm_commit() 0 25 4
A set_error_msg() 0 9 3
D stop_previous_item() 0 55 18
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 set_modified_on() 0 10 1
A set_preview_image() 0 11 1
A sort_tree_array() 0 12 3
A set_seriousgame_mode() 0 23 4
A returnLpItemList() 0 15 2
B create_tree_array() 0 38 11
A set_use_max_score() 0 12 1
A set_previous_item() 0 6 2
A set_theme() 0 11 1
A set_author() 0 10 1
A set_attempt_mode() 0 32 5
A update_display_order() 0 29 5
A set_proximity() 0 15 2
B overview() 0 50 9
A set_expired_on() 0 23 3
B print_recursive() 0 40 10
F display_hotpotatoes_form() 0 155 36
A set_encoding() 0 19 4
A update_scorm_debug() 0 23 4
A update_reinit() 0 23 4
A set_hide_toc_frame() 0 15 2
B get_attempt_mode() 0 21 9
A return_new_tree() 0 34 4
A set_maker() 0 14 2
A set_name() 0 32 3
F processBuildMenuElements() 0 441 53
F save_last() 0 89 23
F display_edit_item() 0 133 21
A display_document() 0 20 2
F build_action_menu() 0 179 15
A display_resources() 0 55 2
A get_extension() 0 5 1
F display_thread_form() 0 192 42
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
F display_link_form() 0 186 35
F display_document_form() 0 367 76
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
F displayFrmReadOutText() 0 278 60
A getAccumulateWorkTimePrerequisite() 0 13 1
A getSelectedIconHtml() 0 12 2
F display_item_form() 0 239 38
A getItemsForForm() 0 39 3
A getItem() 0 7 3
A getCategoryId() 0 3 1
A getCurrentBuildingModeURL() 0 11 5
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 deleteCategory() 0 47 5
A getAccumulateWorkTimeTotalCourse() 0 10 1
F display_quiz_form() 0 179 36
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
F display_forum_form() 0 183 40
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 207 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
F display_student_publication_form() 0 164 31
A setAccumulateWorkTime() 0 14 2
A setAuthorLpItem() 0 27 6

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
        $actionsLeft = $back;
6672
        $actionsLeft .= Display::url(
6673
            Display::return_icon(
6674
                'preview_view.png',
6675
                get_lang('Preview'),
6676
                '',
6677
                ICON_SIZE_MEDIUM
6678
            ),
6679
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6680
                'action' => 'view',
6681
                'lp_id' => $lpId,
6682
                'isStudentView' => 'true',
6683
            ])
6684
        );
6685
6686
        $actionsLeft .= Display::url(
6687
            Display::return_icon(
6688
                'upload_audio.png',
6689
                get_lang('UpdateAllAudioFragments'),
6690
                '',
6691
                ICON_SIZE_MEDIUM
6692
            ),
6693
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6694
                'action' => 'admin_view',
6695
                'lp_id' => $lpId,
6696
                'updateaudio' => 'true',
6697
            ])
6698
        );
6699
6700
        $subscriptionSettings = self::getSubscriptionSettings();
6701
        $request = api_request_uri();
6702
        if (strpos($request, 'edit') === false) {
6703
            $actionsLeft .= Display::url(
6704
                Display::return_icon(
6705
                    'settings.png',
6706
                    get_lang('CourseSettings'),
6707
                    '',
6708
                    ICON_SIZE_MEDIUM
6709
                ),
6710
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6711
                    'action' => 'edit',
6712
                    'lp_id' => $lpId,
6713
                ])
6714
            );
6715
        }
6716
6717
        if ((strpos($request, 'build') === false &&
6718
            strpos($request, 'add_item') === false) ||
6719
            in_array($action, ['add_audio'])
6720
        ) {
6721
            $actionsLeft .= Display::url(
6722
                Display::return_icon(
6723
                    'edit.png',
6724
                    get_lang('Edit'),
6725
                    '',
6726
                    ICON_SIZE_MEDIUM
6727
                ),
6728
                'lp_controller.php?'.http_build_query([
6729
                    'action' => 'build',
6730
                    'lp_id' => $lpId,
6731
                ]).'&'.api_get_cidreq()
6732
            );
6733
        }
6734
6735
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6736
            if ($this->subscribeUsers == 1 &&
6737
                $subscriptionSettings['allow_add_users_to_lp']) {
6738
                $actionsLeft .= Display::url(
6739
                    Display::return_icon(
6740
                        'user.png',
6741
                        get_lang('SubscribeUsersToLp'),
6742
                        '',
6743
                        ICON_SIZE_MEDIUM
6744
                    ),
6745
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6746
                );
6747
            }
6748
        }
6749
6750
        if ($allowExpand) {
6751
            $actionsLeft .= Display::url(
6752
                Display::return_icon(
6753
                    'expand.png',
6754
                    get_lang('Expand'),
6755
                    ['id' => 'expand'],
6756
                    ICON_SIZE_MEDIUM
6757
                ).
6758
                Display::return_icon(
6759
                    'contract.png',
6760
                    get_lang('Collapse'),
6761
                    ['id' => 'contract', 'class' => 'hide'],
6762
                    ICON_SIZE_MEDIUM
6763
                ),
6764
                '#',
6765
                ['role' => 'button', 'id' => 'hide_bar_template']
6766
            );
6767
        }
6768
6769
        if ($showRequirementButtons) {
6770
            $buttons = [
6771
                [
6772
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6773
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6774
                        'action' => 'set_previous_step_as_prerequisite',
6775
                        'lp_id' => $lpId,
6776
                    ]),
6777
                ],
6778
                [
6779
                    'title' => get_lang('ClearAllPrerequisites'),
6780
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6781
                        'action' => 'clear_prerequisites',
6782
                        'lp_id' => $lpId,
6783
                    ]),
6784
                ],
6785
            ];
6786
            $actionsRight = Display::groupButtonWithDropDown(
6787
                get_lang('PrerequisitesOptions'),
6788
                $buttons,
6789
                true
6790
            );
6791
        }
6792
6793
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6794
            $actionsLeft .= Display::url(
6795
                Display::return_icon(
6796
                    'add-groups.png',
6797
                    get_lang('Author'),
6798
                    '',
6799
                    ICON_SIZE_MEDIUM
6800
                ),
6801
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6802
                    'action' => 'author_view',
6803
                    'lp_id' => $lpId,
6804
                ])
6805
            );
6806
        }
6807
6808
        $toolbar = Display::toolbarAction(
6809
            'actions-lp-controller',
6810
            [$actionsLeft, $actionsRight]
6811
        );
6812
6813
        if ($returnString) {
6814
            return $toolbar;
6815
        }
6816
6817
        echo $toolbar;
6818
    }
6819
6820
    /**
6821
     * Creates the default learning path folder.
6822
     *
6823
     * @param array $course
6824
     * @param int   $creatorId
6825
     *
6826
     * @return bool
6827
     */
6828
    public static function generate_learning_path_folder($course, $creatorId = 0)
6829
    {
6830
        // Creating learning_path folder
6831
        $dir = '/learning_path';
6832
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6833
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6834
6835
        $folder = false;
6836
        if (!is_dir($filepath.'/'.$dir)) {
6837
            $folderData = create_unexisting_directory(
6838
                $course,
6839
                $creatorId,
6840
                0,
6841
                null,
6842
                0,
6843
                $filepath,
6844
                $dir,
6845
                get_lang('LearningPaths'),
6846
                0
6847
            );
6848
            if (!empty($folderData)) {
6849
                $folder = true;
6850
            }
6851
        } else {
6852
            $folder = true;
6853
        }
6854
6855
        return $folder;
6856
    }
6857
6858
    /**
6859
     * @param array  $course
6860
     * @param string $lp_name
6861
     * @param int    $creatorId
6862
     *
6863
     * @return array
6864
     */
6865
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6866
    {
6867
        $filepath = '';
6868
        $dir = '/learning_path/';
6869
6870
        if (empty($lp_name)) {
6871
            $lp_name = $this->name;
6872
        }
6873
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6874
        $folder = self::generate_learning_path_folder($course, $creatorId);
6875
6876
        // Limits title size
6877
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6878
        $dir = $dir.$title;
6879
6880
        // Creating LP folder
6881
        $documentId = null;
6882
        if ($folder) {
6883
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6884
            if (!is_dir($filepath.'/'.$dir)) {
6885
                $folderData = create_unexisting_directory(
6886
                    $course,
6887
                    $creatorId,
6888
                    0,
6889
                    0,
6890
                    0,
6891
                    $filepath,
6892
                    $dir,
6893
                    $lp_name
6894
                );
6895
                if (!empty($folderData)) {
6896
                    $folder = true;
6897
                }
6898
6899
                $documentId = $folderData['id'];
6900
            } else {
6901
                $folder = true;
6902
            }
6903
            $dir = $dir.'/';
6904
            if ($folder) {
6905
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6906
            }
6907
        }
6908
6909
        if (empty($documentId)) {
6910
            $dir = api_remove_trailing_slash($dir);
6911
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6912
        }
6913
6914
        $array = [
6915
            'dir' => $dir,
6916
            'filepath' => $filepath,
6917
            'folder' => $folder,
6918
            'id' => $documentId,
6919
        ];
6920
6921
        return $array;
6922
    }
6923
6924
    /**
6925
     * Create a new document //still needs some finetuning.
6926
     *
6927
     * @param array  $courseInfo
6928
     * @param string $content
6929
     * @param string $title
6930
     * @param string $extension
6931
     * @param int    $parentId
6932
     * @param int    $creatorId  creator id
6933
     *
6934
     * @return int
6935
     */
6936
    public function create_document(
6937
        $courseInfo,
6938
        $content = '',
6939
        $title = '',
6940
        $extension = 'html',
6941
        $parentId = 0,
6942
        $creatorId = 0
6943
    ) {
6944
        if (!empty($courseInfo)) {
6945
            $course_id = $courseInfo['real_id'];
6946
        } else {
6947
            $course_id = api_get_course_int_id();
6948
        }
6949
6950
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6951
        $sessionId = api_get_session_id();
6952
6953
        // Generates folder
6954
        $result = $this->generate_lp_folder($courseInfo);
6955
        $dir = $result['dir'];
6956
6957
        if (empty($parentId) || $parentId == '/') {
6958
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6959
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6960
6961
            if ($parentId === '/') {
6962
                $dir = '/';
6963
            }
6964
6965
            // Please, do not modify this dirname formatting.
6966
            if (strstr($dir, '..')) {
6967
                $dir = '/';
6968
            }
6969
6970
            if (!empty($dir[0]) && $dir[0] == '.') {
6971
                $dir = substr($dir, 1);
6972
            }
6973
            if (!empty($dir[0]) && $dir[0] != '/') {
6974
                $dir = '/'.$dir;
6975
            }
6976
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6977
                $dir .= '/';
6978
            }
6979
        } else {
6980
            $parentInfo = DocumentManager::get_document_data_by_id(
6981
                $parentId,
6982
                $courseInfo['code']
6983
            );
6984
            if (!empty($parentInfo)) {
6985
                $dir = $parentInfo['path'].'/';
6986
            }
6987
        }
6988
6989
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6990
        if (!is_dir($filepath)) {
6991
            $dir = '/';
6992
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6993
        }
6994
6995
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6996
        // is already escaped twice when it gets here.
6997
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6998
        if (!empty($title)) {
6999
            $title = api_replace_dangerous_char(stripslashes($title));
7000
        } else {
7001
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7002
        }
7003
7004
        $title = disable_dangerous_file($title);
7005
        $filename = $title;
7006
        $content = !empty($content) ? $content : $_POST['content_lp'];
7007
        $tmp_filename = $filename;
7008
7009
        $i = 0;
7010
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7011
            $tmp_filename = $filename.'_'.++$i;
7012
        }
7013
7014
        $filename = $tmp_filename.'.'.$extension;
7015
        if ($extension == 'html') {
7016
            $content = stripslashes($content);
7017
            $content = str_replace(
7018
                api_get_path(WEB_COURSE_PATH),
7019
                api_get_path(REL_PATH).'courses/',
7020
                $content
7021
            );
7022
7023
            // Change the path of mp3 to absolute.
7024
            // The first regexp deals with :// urls.
7025
            $content = preg_replace(
7026
                "|(flashvars=\"file=)([^:/]+)/|",
7027
                "$1".api_get_path(
7028
                    REL_COURSE_PATH
7029
                ).$courseInfo['path'].'/document/',
7030
                $content
7031
            );
7032
            // The second regexp deals with audio/ urls.
7033
            $content = preg_replace(
7034
                "|(flashvars=\"file=)([^/]+)/|",
7035
                "$1".api_get_path(
7036
                    REL_COURSE_PATH
7037
                ).$courseInfo['path'].'/document/$2/',
7038
                $content
7039
            );
7040
            // For flv player: To prevent edition problem with firefox,
7041
            // we have to use a strange tip (don't blame me please).
7042
            $content = str_replace(
7043
                '</body>',
7044
                '<style type="text/css">body{}</style></body>',
7045
                $content
7046
            );
7047
        }
7048
7049
        if (!file_exists($filepath.$filename)) {
7050
            if ($fp = @fopen($filepath.$filename, 'w')) {
7051
                fputs($fp, $content);
7052
                fclose($fp);
7053
7054
                $file_size = filesize($filepath.$filename);
7055
                $save_file_path = $dir.$filename;
7056
7057
                $document_id = add_document(
7058
                    $courseInfo,
7059
                    $save_file_path,
7060
                    'file',
7061
                    $file_size,
7062
                    $tmp_filename,
7063
                    '',
7064
                    0, //readonly
7065
                    true,
7066
                    null,
7067
                    $sessionId,
7068
                    $creatorId
7069
                );
7070
7071
                if ($document_id) {
7072
                    api_item_property_update(
7073
                        $courseInfo,
7074
                        TOOL_DOCUMENT,
7075
                        $document_id,
7076
                        'DocumentAdded',
7077
                        $creatorId,
7078
                        null,
7079
                        null,
7080
                        null,
7081
                        null,
7082
                        $sessionId
7083
                    );
7084
7085
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7086
                    $new_title = $originalTitle;
7087
7088
                    if ($new_comment || $new_title) {
7089
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7090
                        $ct = '';
7091
                        if ($new_comment) {
7092
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7093
                        }
7094
                        if ($new_title) {
7095
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7096
                        }
7097
7098
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7099
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7100
                        Database::query($sql);
7101
                    }
7102
                }
7103
7104
                return $document_id;
7105
            }
7106
        }
7107
    }
7108
7109
    /**
7110
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7111
     *
7112
     * @param array $_course array
7113
     */
7114
    public function edit_document($_course)
7115
    {
7116
        $course_id = api_get_course_int_id();
7117
        $urlAppend = api_get_configuration_value('url_append');
7118
        // Please, do not modify this dirname formatting.
7119
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7120
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7121
7122
        if (strstr($dir, '..')) {
7123
            $dir = '/';
7124
        }
7125
7126
        if (isset($dir[0]) && $dir[0] == '.') {
7127
            $dir = substr($dir, 1);
7128
        }
7129
7130
        if (isset($dir[0]) && $dir[0] != '/') {
7131
            $dir = '/'.$dir;
7132
        }
7133
7134
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7135
            $dir .= '/';
7136
        }
7137
7138
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7139
        if (!is_dir($filepath)) {
7140
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7141
        }
7142
7143
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7144
7145
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7146
            $document_id = (int) $_POST['path'];
7147
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7148
            if (empty($documentInfo)) {
7149
                // Try with iid
7150
                $table = Database::get_course_table(TABLE_DOCUMENT);
7151
                $sql = "SELECT id, path FROM $table
7152
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7153
                $res_doc = Database::query($sql);
7154
                $row = Database::fetch_array($res_doc);
7155
                if ($row) {
7156
                    $document_id = $row['id'];
7157
                    $documentPath = $row['path'];
7158
                }
7159
            } else {
7160
                $documentPath = $documentInfo['path'];
7161
            }
7162
7163
            $content = stripslashes($_POST['content_lp']);
7164
            $file = $filepath.$documentPath;
7165
7166
            if (!file_exists($file)) {
7167
                return false;
7168
            }
7169
7170
            if ($fp = @fopen($file, 'w')) {
7171
                $content = str_replace(
7172
                    api_get_path(WEB_COURSE_PATH),
7173
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7174
                    $content
7175
                );
7176
                // Change the path of mp3 to absolute.
7177
                // The first regexp deals with :// urls.
7178
                $content = preg_replace(
7179
                    "|(flashvars=\"file=)([^:/]+)/|",
7180
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7181
                    $content
7182
                );
7183
                // The second regexp deals with audio/ urls.
7184
                $content = preg_replace(
7185
                    "|(flashvars=\"file=)([^:/]+)/|",
7186
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7187
                    $content
7188
                );
7189
                fputs($fp, $content);
7190
                fclose($fp);
7191
7192
                $sql = "UPDATE $table_doc SET
7193
                            title='".Database::escape_string($_POST['title'])."'
7194
                        WHERE c_id = $course_id AND id = ".$document_id;
7195
                Database::query($sql);
7196
            }
7197
        }
7198
    }
7199
7200
    /**
7201
     * Displays the selected item, with a panel for manipulating the item.
7202
     *
7203
     * @param int    $item_id
7204
     * @param string $msg
7205
     * @param bool   $show_actions
7206
     *
7207
     * @return string
7208
     */
7209
    public function display_item($item_id, $msg = null, $show_actions = true)
7210
    {
7211
        $course_id = api_get_course_int_id();
7212
        $return = '';
7213
        if (is_numeric($item_id)) {
7214
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7215
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7216
                    WHERE lp.iid = ".intval($item_id);
7217
            $result = Database::query($sql);
7218
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7219
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7220
7221
                // Prevents wrong parent selection for document, see Bug#1251.
7222
                if ($row['item_type'] != 'dir') {
7223
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7224
                }
7225
7226
                if ($show_actions) {
7227
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7228
                }
7229
                $return .= '<div style="padding:10px;">';
7230
7231
                if ($msg != '') {
7232
                    $return .= $msg;
7233
                }
7234
7235
                $return .= '<h3>'.$row['title'].'</h3>';
7236
7237
                switch ($row['item_type']) {
7238
                    case TOOL_THREAD:
7239
                        $link = $this->rl_get_resource_link_for_learnpath(
7240
                            $course_id,
7241
                            $row['lp_id'],
7242
                            $item_id,
7243
                            0
7244
                        );
7245
                        $return .= Display::url(
7246
                            get_lang('GoToThread'),
7247
                            $link,
7248
                            ['class' => 'btn btn-primary']
7249
                        );
7250
                        break;
7251
                    case TOOL_FORUM:
7252
                        $return .= Display::url(
7253
                            get_lang('GoToForum'),
7254
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7255
                            ['class' => 'btn btn-primary']
7256
                        );
7257
                        break;
7258
                    case TOOL_QUIZ:
7259
                        if (!empty($row['path'])) {
7260
                            $exercise = new Exercise();
7261
                            $exercise->read($row['path']);
7262
                            $return .= $exercise->description.'<br />';
7263
                            $return .= Display::url(
7264
                                get_lang('GoToExercise'),
7265
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7266
                                ['class' => 'btn btn-primary']
7267
                            );
7268
                        }
7269
                        break;
7270
                    case TOOL_LP_FINAL_ITEM:
7271
                        $return .= $this->getSavedFinalItem();
7272
                        break;
7273
                    case TOOL_DOCUMENT:
7274
                    case TOOL_READOUT_TEXT:
7275
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7276
                        $sql_doc = "SELECT path FROM $tbl_doc
7277
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7278
                        $result = Database::query($sql_doc);
7279
                        $path_file = Database::result($result, 0, 0);
7280
                        $path_parts = pathinfo($path_file);
7281
                        // TODO: Correct the following naive comparisons.
7282
                        if (in_array($path_parts['extension'], [
7283
                            'html',
7284
                            'txt',
7285
                            'png',
7286
                            'jpg',
7287
                            'JPG',
7288
                            'jpeg',
7289
                            'JPEG',
7290
                            'gif',
7291
                            'swf',
7292
                            'pdf',
7293
                            'htm',
7294
                        ])) {
7295
                            $return .= $this->display_document($row['path'], true, true);
7296
                        }
7297
                        break;
7298
                    case TOOL_HOTPOTATOES:
7299
                        $return .= $this->display_document($row['path'], false, true);
7300
                        break;
7301
                }
7302
                $return .= '</div>';
7303
            }
7304
        }
7305
7306
        return $return;
7307
    }
7308
7309
    /**
7310
     * Shows the needed forms for editing a specific item.
7311
     *
7312
     * @param int $item_id
7313
     *
7314
     * @throws Exception
7315
     * @throws HTML_QuickForm_Error
7316
     *
7317
     * @return string
7318
     */
7319
    public function display_edit_item($item_id, $excludeExtraFields = [])
7320
    {
7321
        $course_id = api_get_course_int_id();
7322
        $return = '';
7323
        $item_id = (int) $item_id;
7324
7325
        if (empty($item_id)) {
7326
            return '';
7327
        }
7328
7329
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7330
        $sql = "SELECT * FROM $tbl_lp_item
7331
                WHERE iid = ".$item_id;
7332
        $res = Database::query($sql);
7333
        $row = Database::fetch_array($res);
7334
        switch ($row['item_type']) {
7335
            case 'dir':
7336
            case 'asset':
7337
            case 'sco':
7338
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7339
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7340
                $return .= $this->display_item_form(
7341
                        $row['item_type'],
7342
                        get_lang('EditCurrentChapter').' :',
7343
                        'edit',
7344
                        $item_id,
7345
                        $row
7346
                    );
7347
            } else {
7348
                $return .= $this->display_item_form(
7349
                        $row['item_type'],
7350
                        get_lang('EditCurrentChapter').' :',
7351
                        'edit_item',
7352
                        $item_id,
7353
                        $row
7354
                    );
7355
            }
7356
                break;
7357
            case TOOL_DOCUMENT:
7358
            case TOOL_READOUT_TEXT:
7359
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7360
                $sql = "SELECT lp.*, doc.path as dir
7361
                        FROM $tbl_lp_item as lp
7362
                        LEFT JOIN $tbl_doc as doc
7363
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7364
                        WHERE
7365
                            doc.c_id = $course_id AND
7366
                            lp.iid = ".$item_id;
7367
                $res_step = Database::query($sql);
7368
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7369
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7370
7371
                if ($row['item_type'] === TOOL_DOCUMENT) {
7372
                    $return .= $this->display_document_form(
7373
                        'edit',
7374
                        $item_id,
7375
                        $row_step,
7376
                        null,
7377
                        $excludeExtraFields
7378
                    );
7379
                }
7380
7381
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7382
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7383
                }
7384
                break;
7385
            case TOOL_LINK:
7386
                $linkId = (int) $row['path'];
7387
                if (!empty($linkId)) {
7388
                    $table = Database::get_course_table(TABLE_LINK);
7389
                    $sql = 'SELECT url FROM '.$table.'
7390
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7391
                    $res_link = Database::query($sql);
7392
                    $row_link = Database::fetch_array($res_link);
7393
                    if (empty($row_link)) {
7394
                        // Try with id
7395
                        $sql = 'SELECT url FROM '.$table.'
7396
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7397
                        $res_link = Database::query($sql);
7398
                        $row_link = Database::fetch_array($res_link);
7399
                    }
7400
7401
                    if (is_array($row_link)) {
7402
                        $row['url'] = $row_link['url'];
7403
                    }
7404
                }
7405
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7406
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7407
                break;
7408
            case TOOL_LP_FINAL_ITEM:
7409
                Session::write('finalItem', true);
7410
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7411
                $sql = "SELECT lp.*, doc.path as dir
7412
                        FROM $tbl_lp_item as lp
7413
                        LEFT JOIN $tbl_doc as doc
7414
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7415
                        WHERE
7416
                            doc.c_id = $course_id AND
7417
                            lp.iid = ".$item_id;
7418
                $res_step = Database::query($sql);
7419
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7420
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7421
                $return .= $this->display_document_form(
7422
                    'edit',
7423
                    $item_id,
7424
                    $row_step,
7425
                    null,
7426
                    $excludeExtraFields
7427
                );
7428
                break;
7429
            case TOOL_QUIZ:
7430
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7431
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7432
                break;
7433
            case TOOL_HOTPOTATOES:
7434
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7435
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7436
                break;
7437
            case TOOL_STUDENTPUBLICATION:
7438
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7439
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7440
                break;
7441
            case TOOL_FORUM:
7442
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7443
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7444
                break;
7445
            case TOOL_THREAD:
7446
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7447
                $return .= $this->display_thread_form('edit', $item_id, $row);
7448
                break;
7449
        }
7450
7451
        return $return;
7452
    }
7453
7454
    /**
7455
     * Function that displays a list with al the resources that
7456
     * could be added to the learning path.
7457
     *
7458
     * @throws Exception
7459
     * @throws HTML_QuickForm_Error
7460
     *
7461
     * @return bool
7462
     */
7463
    public function display_resources()
7464
    {
7465
        $course_code = api_get_course_id();
7466
7467
        // Get all the docs.
7468
        $documents = $this->get_documents(true);
7469
7470
        // Get all the exercises.
7471
        $exercises = $this->get_exercises();
7472
7473
        // Get all the links.
7474
        $links = $this->get_links();
7475
7476
        // Get all the student publications.
7477
        $works = $this->get_student_publications();
7478
7479
        // Get all the forums.
7480
        $forums = $this->get_forums(null, $course_code);
7481
7482
        // Get the final item form (see BT#11048) .
7483
        $finish = $this->getFinalItemForm();
7484
7485
        $headers = [
7486
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7487
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7488
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7489
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7490
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7491
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7492
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7493
        ];
7494
7495
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7496
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7497
7498
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7499
7500
        echo Display::tabs(
7501
            $headers,
7502
            [
7503
                $documents,
7504
                $exercises,
7505
                $links,
7506
                $works,
7507
                $forums,
7508
                $dir,
7509
                $finish,
7510
            ],
7511
            'resource_tab',
7512
            [],
7513
            [],
7514
            $selected
7515
        );
7516
7517
        return true;
7518
    }
7519
7520
    /**
7521
     * Returns the extension of a document.
7522
     *
7523
     * @param string $filename
7524
     *
7525
     * @return string Extension (part after the last dot)
7526
     */
7527
    public function get_extension($filename)
7528
    {
7529
        $explode = explode('.', $filename);
7530
7531
        return $explode[count($explode) - 1];
7532
    }
7533
7534
    /**
7535
     * Displays a document by id.
7536
     *
7537
     * @param int  $id
7538
     * @param bool $show_title
7539
     * @param bool $iframe
7540
     * @param bool $edit_link
7541
     *
7542
     * @return string
7543
     */
7544
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7545
    {
7546
        $_course = api_get_course_info();
7547
        $course_id = api_get_course_int_id();
7548
        $id = (int) $id;
7549
        $return = '';
7550
        $table = Database::get_course_table(TABLE_DOCUMENT);
7551
        $sql_doc = "SELECT * FROM $table
7552
                    WHERE c_id = $course_id AND iid = $id";
7553
        $res_doc = Database::query($sql_doc);
7554
        $row_doc = Database::fetch_array($res_doc);
7555
7556
        // TODO: Add a path filter.
7557
        if ($iframe) {
7558
            $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>';
7559
        } else {
7560
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7561
        }
7562
7563
        return $return;
7564
    }
7565
7566
    /**
7567
     * Return HTML form to add/edit a quiz.
7568
     *
7569
     * @param string $action     Action (add/edit)
7570
     * @param int    $id         Item ID if already exists
7571
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7572
     *
7573
     * @throws Exception
7574
     *
7575
     * @return string HTML form
7576
     */
7577
    public function display_quiz_form(
7578
        $action = 'add',
7579
        $id = 0,
7580
        $extra_info = '',
7581
        $excludeExtraFields = []
7582
    ) {
7583
        $course_id = api_get_course_int_id();
7584
        $id = (int) $id;
7585
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7586
7587
        if ($id != 0 && is_array($extra_info)) {
7588
            $item_title = $extra_info['title'];
7589
            $item_description = $extra_info['description'];
7590
        } elseif (is_numeric($extra_info)) {
7591
            $sql = "SELECT title, description
7592
                    FROM $tbl_quiz
7593
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7594
7595
            $result = Database::query($sql);
7596
            $row = Database::fetch_array($result);
7597
            $item_title = $row['title'];
7598
            $item_description = $row['description'];
7599
        } else {
7600
            $item_title = '';
7601
            $item_description = '';
7602
        }
7603
        $item_title = Security::remove_XSS($item_title);
7604
        $item_description = Security::remove_XSS($item_description);
7605
7606
        $parent = 0;
7607
        if ($id != 0 && is_array($extra_info)) {
7608
            $parent = $extra_info['parent_item_id'];
7609
        }
7610
7611
        $arrLP = $this->getItemsForForm();
7612
        $this->tree_array($arrLP);
7613
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7614
        unset($this->arrMenu);
7615
7616
        $form = new FormValidator(
7617
            'quiz_form',
7618
            'POST',
7619
            $this->getCurrentBuildingModeURL()
7620
        );
7621
        $defaults = [];
7622
7623
        if ($action === 'add') {
7624
            $legend = get_lang('CreateTheExercise');
7625
        } elseif ($action === 'move') {
7626
            $legend = get_lang('MoveTheCurrentExercise');
7627
        } else {
7628
            $legend = get_lang('EditCurrentExecice');
7629
        }
7630
7631
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7632
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7633
        }
7634
7635
        $form->addHeader($legend);
7636
7637
        if ($action != 'move') {
7638
            $this->setItemTitle($form);
7639
            $defaults['title'] = $item_title;
7640
        }
7641
7642
        // Select for Parent item, root or chapter
7643
        $selectParent = $form->addSelect(
7644
            'parent',
7645
            get_lang('Parent'),
7646
            [],
7647
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7648
        );
7649
        $selectParent->addOption($this->name, 0);
7650
7651
        $arrHide = [
7652
            $id,
7653
        ];
7654
        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...
7655
            if ($action != 'add') {
7656
                if (
7657
                    ($arrLP[$i]['item_type'] == 'dir') &&
7658
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7659
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7660
                ) {
7661
                    $selectParent->addOption(
7662
                        $arrLP[$i]['title'],
7663
                        $arrLP[$i]['id'],
7664
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7665
                    );
7666
7667
                    if ($parent == $arrLP[$i]['id']) {
7668
                        $selectParent->setSelected($arrLP[$i]['id']);
7669
                    }
7670
                } else {
7671
                    $arrHide[] = $arrLP[$i]['id'];
7672
                }
7673
            } else {
7674
                if ($arrLP[$i]['item_type'] == 'dir') {
7675
                    $selectParent->addOption(
7676
                        $arrLP[$i]['title'],
7677
                        $arrLP[$i]['id'],
7678
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7679
                    );
7680
7681
                    if ($parent == $arrLP[$i]['id']) {
7682
                        $selectParent->setSelected($arrLP[$i]['id']);
7683
                    }
7684
                }
7685
            }
7686
        }
7687
7688
        if (is_array($arrLP)) {
7689
            reset($arrLP);
7690
        }
7691
7692
        $selectPrevious = $form->addSelect(
7693
            'previous',
7694
            get_lang('Position'),
7695
            [],
7696
            ['id' => 'previous']
7697
        );
7698
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7699
7700
        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...
7701
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7702
                $arrLP[$i]['id'] != $id
7703
            ) {
7704
                $selectPrevious->addOption(
7705
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7706
                    $arrLP[$i]['id']
7707
                );
7708
7709
                if (is_array($extra_info)) {
7710
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7711
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7712
                    }
7713
                } elseif ($action == 'add') {
7714
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7715
                }
7716
            }
7717
        }
7718
7719
        if ($action != 'move') {
7720
            $arrHide = [];
7721
            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...
7722
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7723
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7724
                }
7725
            }
7726
        }
7727
7728
        if ('edit' === $action) {
7729
            $extraField = new ExtraField('lp_item');
7730
            $extraField->addElements($form, $id, $excludeExtraFields);
7731
        }
7732
7733
        if ($action === 'add') {
7734
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7735
        } else {
7736
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7737
        }
7738
7739
        if ($action === 'move') {
7740
            $form->addHidden('title', $item_title);
7741
            $form->addHidden('description', $item_description);
7742
        }
7743
7744
        if (is_numeric($extra_info)) {
7745
            $form->addHidden('path', $extra_info);
7746
        } elseif (is_array($extra_info)) {
7747
            $form->addHidden('path', $extra_info['path']);
7748
        }
7749
7750
        $form->addHidden('type', TOOL_QUIZ);
7751
        $form->addHidden('post_time', time());
7752
        $this->setAuthorLpItem($form);
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(
7936
        $action = 'add',
7937
        $id = 0,
7938
        $extra_info = '',
7939
        $excludeExtraFields = []
7940
    ) {
7941
        $course_id = api_get_course_int_id();
7942
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7943
7944
        $item_title = '';
7945
        $item_description = '';
7946
7947
        if ($id != 0 && is_array($extra_info)) {
7948
            $item_title = stripslashes($extra_info['title']);
7949
        } elseif (is_numeric($extra_info)) {
7950
            $sql = "SELECT forum_title as title, forum_comment as comment
7951
                    FROM $tbl_forum
7952
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7953
7954
            $result = Database::query($sql);
7955
            $row = Database::fetch_array($result);
7956
7957
            $item_title = $row['title'];
7958
            $item_description = $row['comment'];
7959
        }
7960
        $parent = 0;
7961
        if ($id != 0 && is_array($extra_info)) {
7962
            $parent = $extra_info['parent_item_id'];
7963
        }
7964
        $arrLP = $this->getItemsForForm();
7965
        $this->tree_array($arrLP);
7966
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7967
        unset($this->arrMenu);
7968
7969
        if ($action == 'add') {
7970
            $legend = get_lang('CreateTheForum');
7971
        } elseif ($action == 'move') {
7972
            $legend = get_lang('MoveTheCurrentForum');
7973
        } else {
7974
            $legend = get_lang('EditCurrentForum');
7975
        }
7976
7977
        $form = new FormValidator(
7978
            'forum_form',
7979
            'POST',
7980
            $this->getCurrentBuildingModeURL()
7981
        );
7982
        $defaults = [];
7983
7984
        $form->addHeader($legend);
7985
7986
        if ($action != 'move') {
7987
            $this->setItemTitle($form);
7988
            $defaults['title'] = $item_title;
7989
        }
7990
7991
        $selectParent = $form->addSelect(
7992
            'parent',
7993
            get_lang('Parent'),
7994
            [],
7995
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7996
        );
7997
        $selectParent->addOption($this->name, 0);
7998
        $arrHide = [
7999
            $id,
8000
        ];
8001
        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...
8002
            if ($action != 'add') {
8003
                if ($arrLP[$i]['item_type'] == 'dir' &&
8004
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8005
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8006
                ) {
8007
                    $selectParent->addOption(
8008
                        $arrLP[$i]['title'],
8009
                        $arrLP[$i]['id'],
8010
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8011
                    );
8012
8013
                    if ($parent == $arrLP[$i]['id']) {
8014
                        $selectParent->setSelected($arrLP[$i]['id']);
8015
                    }
8016
                } else {
8017
                    $arrHide[] = $arrLP[$i]['id'];
8018
                }
8019
            } else {
8020
                if ($arrLP[$i]['item_type'] == 'dir') {
8021
                    $selectParent->addOption(
8022
                        $arrLP[$i]['title'],
8023
                        $arrLP[$i]['id'],
8024
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8025
                    );
8026
8027
                    if ($parent == $arrLP[$i]['id']) {
8028
                        $selectParent->setSelected($arrLP[$i]['id']);
8029
                    }
8030
                }
8031
            }
8032
        }
8033
8034
        if (is_array($arrLP)) {
8035
            reset($arrLP);
8036
        }
8037
8038
        $selectPrevious = $form->addSelect(
8039
            'previous',
8040
            get_lang('Position'),
8041
            [],
8042
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8043
        );
8044
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8045
8046
        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...
8047
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8048
                $arrLP[$i]['id'] != $id
8049
            ) {
8050
                $selectPrevious->addOption(
8051
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8052
                    $arrLP[$i]['id']
8053
                );
8054
8055
                if (isset($extra_info['previous_item_id']) &&
8056
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8057
                ) {
8058
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8059
                } elseif ($action == 'add') {
8060
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8061
                }
8062
            }
8063
        }
8064
8065
        if ($action != 'move') {
8066
            $id_prerequisite = 0;
8067
            if (is_array($arrLP)) {
8068
                foreach ($arrLP as $key => $value) {
8069
                    if ($value['id'] == $id) {
8070
                        $id_prerequisite = $value['prerequisite'];
8071
                        break;
8072
                    }
8073
                }
8074
            }
8075
8076
            $arrHide = [];
8077
            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...
8078
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8079
                    if (isset($extra_info['previous_item_id']) &&
8080
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8081
                    ) {
8082
                        $s_selected_position = $arrLP[$i]['id'];
8083
                    } elseif ($action == 'add') {
8084
                        $s_selected_position = 0;
8085
                    }
8086
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8087
                }
8088
            }
8089
        }
8090
8091
        if ('edit' === $action) {
8092
            $extraField = new ExtraField('lp_item');
8093
            $extraField->addElements($form, $id, $excludeExtraFields);
8094
        }
8095
8096
        if ($action == 'add') {
8097
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8098
        } else {
8099
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8100
        }
8101
8102
        if ($action == 'move') {
8103
            $form->addHidden('title', $item_title);
8104
            $form->addHidden('description', $item_description);
8105
        }
8106
8107
        if (is_numeric($extra_info)) {
8108
            $form->addHidden('path', $extra_info);
8109
        } elseif (is_array($extra_info)) {
8110
            $form->addHidden('path', $extra_info['path']);
8111
        }
8112
        $form->addHidden('type', TOOL_FORUM);
8113
        $form->addHidden('post_time', time());
8114
        $this->setAuthorLpItem($form);
8115
        $form->setDefaults($defaults);
8116
8117
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8118
    }
8119
8120
    /**
8121
     * Return HTML form to add/edit forum threads.
8122
     *
8123
     * @param string $action
8124
     * @param int    $id         Item ID if already exists in learning path
8125
     * @param string $extra_info
8126
     *
8127
     * @throws Exception
8128
     *
8129
     * @return string HTML form
8130
     */
8131
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8132
    {
8133
        $course_id = api_get_course_int_id();
8134
        if (empty($course_id)) {
8135
            return null;
8136
        }
8137
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8138
8139
        $item_title = '';
8140
        $item_description = '';
8141
        if ($id != 0 && is_array($extra_info)) {
8142
            $item_title = stripslashes($extra_info['title']);
8143
        } elseif (is_numeric($extra_info)) {
8144
            $sql = "SELECT thread_title as title FROM $tbl_forum
8145
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8146
8147
            $result = Database::query($sql);
8148
            $row = Database::fetch_array($result);
8149
8150
            $item_title = $row['title'];
8151
            $item_description = '';
8152
        }
8153
8154
        $parent = 0;
8155
        if ($id != 0 && is_array($extra_info)) {
8156
            $parent = $extra_info['parent_item_id'];
8157
        }
8158
8159
        $arrLP = $this->getItemsForForm();
8160
        $this->tree_array($arrLP);
8161
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8162
        unset($this->arrMenu);
8163
8164
        $form = new FormValidator(
8165
            'thread_form',
8166
            'POST',
8167
            $this->getCurrentBuildingModeURL()
8168
        );
8169
        $defaults = [];
8170
8171
        if ($action == 'add') {
8172
            $legend = get_lang('CreateTheForum');
8173
        } elseif ($action == 'move') {
8174
            $legend = get_lang('MoveTheCurrentForum');
8175
        } else {
8176
            $legend = get_lang('EditCurrentForum');
8177
        }
8178
8179
        $form->addHeader($legend);
8180
        $selectParent = $form->addSelect(
8181
            'parent',
8182
            get_lang('Parent'),
8183
            [],
8184
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8185
        );
8186
        $selectParent->addOption($this->name, 0);
8187
8188
        $arrHide = [
8189
            $id,
8190
        ];
8191
8192
        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...
8193
            if ($action != 'add') {
8194
                if (
8195
                    ($arrLP[$i]['item_type'] == 'dir') &&
8196
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8197
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8198
                ) {
8199
                    $selectParent->addOption(
8200
                        $arrLP[$i]['title'],
8201
                        $arrLP[$i]['id'],
8202
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8203
                    );
8204
8205
                    if ($parent == $arrLP[$i]['id']) {
8206
                        $selectParent->setSelected($arrLP[$i]['id']);
8207
                    }
8208
                } else {
8209
                    $arrHide[] = $arrLP[$i]['id'];
8210
                }
8211
            } else {
8212
                if ($arrLP[$i]['item_type'] == 'dir') {
8213
                    $selectParent->addOption(
8214
                        $arrLP[$i]['title'],
8215
                        $arrLP[$i]['id'],
8216
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8217
                    );
8218
8219
                    if ($parent == $arrLP[$i]['id']) {
8220
                        $selectParent->setSelected($arrLP[$i]['id']);
8221
                    }
8222
                }
8223
            }
8224
        }
8225
8226
        if ($arrLP != null) {
8227
            reset($arrLP);
8228
        }
8229
8230
        $selectPrevious = $form->addSelect(
8231
            'previous',
8232
            get_lang('Position'),
8233
            [],
8234
            ['id' => 'previous']
8235
        );
8236
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8237
8238
        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...
8239
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8240
                $selectPrevious->addOption(
8241
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8242
                    $arrLP[$i]['id']
8243
                );
8244
8245
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8246
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8247
                } elseif ($action == 'add') {
8248
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8249
                }
8250
            }
8251
        }
8252
8253
        if ($action != 'move') {
8254
            $this->setItemTitle($form);
8255
            $defaults['title'] = $item_title;
8256
8257
            $id_prerequisite = 0;
8258
            if ($arrLP != null) {
8259
                foreach ($arrLP as $key => $value) {
8260
                    if ($value['id'] == $id) {
8261
                        $id_prerequisite = $value['prerequisite'];
8262
                        break;
8263
                    }
8264
                }
8265
            }
8266
8267
            $arrHide = [];
8268
            $s_selected_position = 0;
8269
            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...
8270
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8271
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8272
                        $s_selected_position = $arrLP[$i]['id'];
8273
                    } elseif ($action == 'add') {
8274
                        $s_selected_position = 0;
8275
                    }
8276
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8277
                }
8278
            }
8279
8280
            $selectPrerequisites = $form->addSelect(
8281
                'prerequisites',
8282
                get_lang('LearnpathPrerequisites'),
8283
                [],
8284
                ['id' => 'prerequisites']
8285
            );
8286
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8287
8288
            foreach ($arrHide as $key => $value) {
8289
                $selectPrerequisites->addOption($value['value'], $key);
8290
8291
                if ($key == $s_selected_position && $action == 'add') {
8292
                    $selectPrerequisites->setSelected($key);
8293
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8294
                    $selectPrerequisites->setSelected($key);
8295
                }
8296
            }
8297
        }
8298
8299
        if ('edit' === $action) {
8300
            $extraField = new ExtraField('lp_item');
8301
            $extraField->addElements($form, $id);
8302
        }
8303
8304
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8305
8306
        if ($action == 'move') {
8307
            $form->addHidden('title', $item_title);
8308
            $form->addHidden('description', $item_description);
8309
        }
8310
8311
        if (is_numeric($extra_info)) {
8312
            $form->addHidden('path', $extra_info);
8313
        } elseif (is_array($extra_info)) {
8314
            $form->addHidden('path', $extra_info['path']);
8315
        }
8316
8317
        $form->addHidden('type', TOOL_THREAD);
8318
        $form->addHidden('post_time', time());
8319
        $this->setAuthorLpItem($form);
8320
        $form->setDefaults($defaults);
8321
8322
        return $form->returnForm();
8323
    }
8324
8325
    /**
8326
     * Return the HTML form to display an item (generally a dir item).
8327
     *
8328
     * @param string $item_type
8329
     * @param string $title
8330
     * @param string $action
8331
     * @param int    $id
8332
     * @param string $extra_info
8333
     *
8334
     * @throws Exception
8335
     * @throws HTML_QuickForm_Error
8336
     *
8337
     * @return string HTML form
8338
     */
8339
    public function display_item_form(
8340
        $item_type,
8341
        $title = '',
8342
        $action = 'add_item',
8343
        $id = 0,
8344
        $extra_info = 'new'
8345
    ) {
8346
        $_course = api_get_course_info();
8347
8348
        global $charset;
8349
8350
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8351
        $item_title = '';
8352
        $item_description = '';
8353
        $item_path_fck = '';
8354
8355
        $parent = 0;
8356
        $previousId = null;
8357
        if ($id != 0 && is_array($extra_info)) {
8358
            $item_title = $extra_info['title'];
8359
            $item_description = $extra_info['description'];
8360
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8361
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8362
            $parent = $extra_info['parent_item_id'];
8363
            $previousId = $extra_info['previous_item_id'];
8364
        }
8365
8366
        if ($extra_info instanceof learnpathItem) {
8367
            $item_title = $extra_info->get_title();
8368
            $item_description = $extra_info->get_description();
8369
            $path = $extra_info->get_path();
8370
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8371
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8372
            $parent = $extra_info->get_parent();
8373
            $previousId = $extra_info->previous;
8374
        }
8375
8376
        $id = (int) $id;
8377
        $sql = "SELECT * FROM $tbl_lp_item
8378
                WHERE
8379
                    lp_id = ".$this->lp_id." AND
8380
                    iid != $id";
8381
8382
        if ($item_type === 'dir') {
8383
            $sql .= " AND parent_item_id = 0";
8384
        }
8385
8386
        $result = Database::query($sql);
8387
        $arrLP = [];
8388
        while ($row = Database::fetch_array($result)) {
8389
            $arrLP[] = [
8390
                'id' => $row['iid'],
8391
                'item_type' => $row['item_type'],
8392
                'title' => $this->cleanItemTitle($row['title']),
8393
                'title_raw' => $row['title'],
8394
                'path' => $row['path'],
8395
                'description' => $row['description'],
8396
                'parent_item_id' => $row['parent_item_id'],
8397
                'previous_item_id' => $row['previous_item_id'],
8398
                'next_item_id' => $row['next_item_id'],
8399
                'max_score' => $row['max_score'],
8400
                'min_score' => $row['min_score'],
8401
                'mastery_score' => $row['mastery_score'],
8402
                'prerequisite' => $row['prerequisite'],
8403
                'display_order' => $row['display_order'],
8404
            ];
8405
        }
8406
8407
        $this->tree_array($arrLP);
8408
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8409
        unset($this->arrMenu);
8410
8411
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8412
8413
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8414
        $defaults['title'] = api_html_entity_decode(
8415
            $item_title,
8416
            ENT_QUOTES,
8417
            $charset
8418
        );
8419
        $defaults['description'] = $item_description;
8420
8421
        $form->addHeader($title);
8422
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8423
        $arrHide[0]['padding'] = 20;
8424
        $charset = api_get_system_encoding();
8425
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8426
            if ($action != 'add') {
8427
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8428
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8429
                ) {
8430
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8431
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8432
                    if ($parent == $arrLP[$i]['id']) {
8433
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8434
                    }
8435
                }
8436
            } else {
8437
                if ($arrLP[$i]['item_type'] === 'dir') {
8438
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8439
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8440
                    if ($parent == $arrLP[$i]['id']) {
8441
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8442
                    }
8443
                }
8444
            }
8445
        }
8446
8447
        if ($action !== 'move') {
8448
            $this->setItemTitle($form);
8449
        } else {
8450
            $form->addElement('hidden', 'title');
8451
        }
8452
8453
        $parentSelect = $form->addElement(
8454
            'select',
8455
            'parent',
8456
            get_lang('Parent'),
8457
            '',
8458
            [
8459
                'id' => 'idParent',
8460
                'onchange' => 'javascript: load_cbo(this.value);',
8461
            ]
8462
        );
8463
8464
        foreach ($arrHide as $key => $value) {
8465
            $parentSelect->addOption(
8466
                $value['value'],
8467
                $key,
8468
                'style="padding-left:'.$value['padding'].'px;"'
8469
            );
8470
            $lastPosition = $key;
8471
        }
8472
8473
        if (!empty($s_selected_parent)) {
8474
            $parentSelect->setSelected($s_selected_parent);
8475
        }
8476
8477
        if (is_array($arrLP)) {
8478
            reset($arrLP);
8479
        }
8480
8481
        $arrHide = [];
8482
        // POSITION
8483
        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...
8484
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8485
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8486
                //this is the same!
8487
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8488
                    $s_selected_position = $arrLP[$i]['id'];
8489
                } elseif ($action === 'add') {
8490
                    $s_selected_position = $arrLP[$i]['id'];
8491
                }
8492
8493
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8494
            }
8495
        }
8496
8497
        $position = $form->addElement(
8498
            'select',
8499
            'previous',
8500
            get_lang('Position'),
8501
            '',
8502
            ['id' => 'previous']
8503
        );
8504
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8505
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8506
8507
        $lastPosition = null;
8508
        foreach ($arrHide as $key => $value) {
8509
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8510
            $lastPosition = $key;
8511
        }
8512
8513
        if (!empty($s_selected_position)) {
8514
            $position->setSelected($s_selected_position);
8515
        }
8516
8517
        // When new chapter add at the end
8518
        if ($action === 'add_item') {
8519
            $position->setSelected($lastPosition);
8520
        }
8521
8522
        if (is_array($arrLP)) {
8523
            reset($arrLP);
8524
        }
8525
8526
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8527
8528
        //fix in order to use the tab
8529
        if ($item_type === 'dir') {
8530
            $form->addElement('hidden', 'type', 'dir');
8531
        }
8532
8533
        $extension = null;
8534
        if (!empty($item_path)) {
8535
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8536
        }
8537
8538
        //assets can't be modified
8539
        //$item_type == 'asset' ||
8540
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8541
            if ($item_type === 'sco') {
8542
                $form->addElement(
8543
                    'html',
8544
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8545
                );
8546
            }
8547
            $renderer = $form->defaultRenderer();
8548
            $renderer->setElementTemplate(
8549
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8550
                'content_lp'
8551
            );
8552
8553
            $relative_prefix = '';
8554
            $editor_config = [
8555
                'ToolbarSet' => 'LearningPathDocuments',
8556
                'Width' => '100%',
8557
                'Height' => '500',
8558
                'FullPage' => true,
8559
                'CreateDocumentDir' => $relative_prefix,
8560
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8561
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8562
            ];
8563
8564
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8565
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8566
            $defaults['content_lp'] = file_get_contents($content_path);
8567
        }
8568
8569
        if (!empty($id)) {
8570
            $form->addHidden('id', $id);
8571
        }
8572
8573
        $form->addElement('hidden', 'type', $item_type);
8574
        $form->addElement('hidden', 'post_time', time());
8575
        $form->setDefaults($defaults);
8576
8577
        return $form->returnForm();
8578
    }
8579
8580
    /**
8581
     * @return string
8582
     */
8583
    public function getCurrentBuildingModeURL()
8584
    {
8585
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8586
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8587
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8588
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8589
8590
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8591
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8592
8593
        return $currentUrl;
8594
    }
8595
8596
    /**
8597
     * Returns the form to update or create a document.
8598
     *
8599
     * @param string        $action     (add/edit)
8600
     * @param int           $id         ID of the lp_item (if already exists)
8601
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
8602
     * @param learnpathItem $item
8603
     *
8604
     * @return string HTML form
8605
     */
8606
    public function display_document_form(
8607
        $action = 'add',
8608
        $id = 0,
8609
        $extra_info = 'new',
8610
        $item = null,
8611
        $excludeExtraFields = []
8612
    ) {
8613
        $course_id = api_get_course_int_id();
8614
        $_course = api_get_course_info();
8615
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8616
8617
        $no_display_edit_textarea = false;
8618
        //If action==edit document
8619
        //We don't display the document form if it's not an editable document (html or txt file)
8620
        if ($action === 'edit') {
8621
            if (is_array($extra_info)) {
8622
                $path_parts = pathinfo($extra_info['dir']);
8623
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
8624
                    $no_display_edit_textarea = true;
8625
                }
8626
            }
8627
        }
8628
        $no_display_add = false;
8629
        // If action==add an existing document
8630
        // We don't display the document form if it's not an editable document (html or txt file).
8631
        if ($action === 'add') {
8632
            if (is_numeric($extra_info)) {
8633
                $extra_info = (int) $extra_info;
8634
                $sql_doc = "SELECT path FROM $tbl_doc
8635
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8636
                $result = Database::query($sql_doc);
8637
                $path_file = Database::result($result, 0, 0);
8638
                $path_parts = pathinfo($path_file);
8639
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8640
                    $no_display_add = true;
8641
                }
8642
            }
8643
        }
8644
8645
        $item_title = '';
8646
        $item_description = '';
8647
        if ($id != 0 && is_array($extra_info)) {
8648
            $item_title = stripslashes($extra_info['title']);
8649
            $item_description = stripslashes($extra_info['description']);
8650
            if (empty($item_title)) {
8651
                $path_parts = pathinfo($extra_info['path']);
8652
                $item_title = stripslashes($path_parts['filename']);
8653
            }
8654
        } elseif (is_numeric($extra_info)) {
8655
            $sql = "SELECT path, title FROM $tbl_doc
8656
                    WHERE
8657
                        c_id = ".$course_id." AND
8658
                        iid = ".intval($extra_info);
8659
            $result = Database::query($sql);
8660
            $row = Database::fetch_array($result);
8661
            $item_title = $row['title'];
8662
            $item_title = str_replace('_', ' ', $item_title);
8663
            if (empty($item_title)) {
8664
                $path_parts = pathinfo($row['path']);
8665
                $item_title = stripslashes($path_parts['filename']);
8666
            }
8667
        }
8668
8669
        $return = '<legend>';
8670
        $parent = 0;
8671
        if ($id != 0 && is_array($extra_info)) {
8672
            $parent = $extra_info['parent_item_id'];
8673
        }
8674
8675
        $selectedPosition = 0;
8676
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
8677
            $selectedPosition = $extra_info['previous_item_id'];
8678
        }
8679
8680
        if ($item instanceof learnpathItem) {
8681
            $item_title = stripslashes($item->get_title());
8682
            $item_description = stripslashes($item->get_description());
8683
            $selectedPosition = $item->previous;
8684
            $parent = $item->parent;
8685
        }
8686
8687
        $arrLP = $this->getItemsForForm();
8688
        $this->tree_array($arrLP);
8689
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8690
        unset($this->arrMenu);
8691
8692
        if ($action === 'add') {
8693
            $return .= get_lang('CreateTheDocument');
8694
        } elseif ($action === 'move') {
8695
            $return .= get_lang('MoveTheCurrentDocument');
8696
        } else {
8697
            $return .= get_lang('EditTheCurrentDocument');
8698
        }
8699
        $return .= '</legend>';
8700
8701
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8702
            $return .= Display::return_message(
8703
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8704
                false
8705
            );
8706
        }
8707
        $form = new FormValidator(
8708
            'form',
8709
            'POST',
8710
            $this->getCurrentBuildingModeURL(),
8711
            '',
8712
            ['enctype' => 'multipart/form-data']
8713
        );
8714
        $defaults['title'] = Security::remove_XSS($item_title);
8715
        if (empty($item_title)) {
8716
            $defaults['title'] = Security::remove_XSS($item_title);
8717
        }
8718
        $defaults['description'] = $item_description;
8719
        $form->addElement('html', $return);
8720
8721
        if ($action !== 'move') {
8722
            $data = $this->generate_lp_folder($_course);
8723
            if ($action !== 'edit') {
8724
                $folders = DocumentManager::get_all_document_folders(
8725
                    $_course,
8726
                    0,
8727
                    true
8728
                );
8729
                DocumentManager::build_directory_selector(
8730
                    $folders,
8731
                    '',
8732
                    [],
8733
                    true,
8734
                    $form,
8735
                    'directory_parent_id'
8736
                );
8737
            }
8738
8739
            if (isset($data['id'])) {
8740
                $defaults['directory_parent_id'] = $data['id'];
8741
            }
8742
            $this->setItemTitle($form);
8743
        }
8744
8745
        $arrHide[0]['value'] = $this->name;
8746
        $arrHide[0]['padding'] = 20;
8747
8748
        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...
8749
            if ($action !== 'add') {
8750
                if ($arrLP[$i]['item_type'] === 'dir' &&
8751
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8752
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8753
                ) {
8754
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8755
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8756
                }
8757
            } else {
8758
                if ($arrLP[$i]['item_type'] == 'dir') {
8759
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8760
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8761
                }
8762
            }
8763
        }
8764
8765
        $parentSelect = $form->addSelect(
8766
            'parent',
8767
            get_lang('Parent'),
8768
            [],
8769
            [
8770
                'id' => 'idParent',
8771
                'onchange' => 'javascript: load_cbo(this.value);',
8772
            ]
8773
        );
8774
8775
        $my_count = 0;
8776
        foreach ($arrHide as $key => $value) {
8777
            if ($my_count != 0) {
8778
                // The LP name is also the first section and is not in the same charset like the other sections.
8779
                $value['value'] = Security::remove_XSS($value['value']);
8780
                $parentSelect->addOption(
8781
                    $value['value'],
8782
                    $key,
8783
                    'style="padding-left:'.$value['padding'].'px;"'
8784
                );
8785
            } else {
8786
                $value['value'] = Security::remove_XSS($value['value']);
8787
                $parentSelect->addOption(
8788
                    $value['value'],
8789
                    $key,
8790
                    'style="padding-left:'.$value['padding'].'px;"'
8791
                );
8792
            }
8793
            $my_count++;
8794
        }
8795
8796
        if (!empty($id)) {
8797
            $parentSelect->setSelected($parent);
8798
        } else {
8799
            $parent_item_id = Session::read('parent_item_id', 0);
8800
            $parentSelect->setSelected($parent_item_id);
8801
        }
8802
8803
        if (is_array($arrLP)) {
8804
            reset($arrLP);
8805
        }
8806
8807
        $arrHide = [];
8808
        // POSITION
8809
        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...
8810
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8811
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8812
            ) {
8813
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8814
            }
8815
        }
8816
8817
        $position = $form->addSelect(
8818
            'previous',
8819
            get_lang('Position'),
8820
            [],
8821
            ['id' => 'previous']
8822
        );
8823
8824
        $position->addOption(get_lang('FirstPosition'), 0);
8825
        foreach ($arrHide as $key => $value) {
8826
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8827
            $position->addOption(
8828
                $value['value'],
8829
                $key,
8830
                'style="padding-left:'.$padding.'px;"'
8831
            );
8832
        }
8833
        $position->setSelected($selectedPosition);
8834
8835
        if (is_array($arrLP)) {
8836
            reset($arrLP);
8837
        }
8838
8839
        if ('edit' === $action) {
8840
            $extraField = new ExtraField('lp_item');
8841
            $extraField->addElements($form, $id, $excludeExtraFields);
8842
        }
8843
8844
        if ($action !== 'move') {
8845
            $arrHide = [];
8846
            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...
8847
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8848
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8849
                ) {
8850
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8851
                }
8852
            }
8853
8854
            if (!$no_display_add) {
8855
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8856
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8857
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8858
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8859
                ) {
8860
                    if (isset($_POST['content'])) {
8861
                        $content = stripslashes($_POST['content']);
8862
                    } elseif (is_array($extra_info)) {
8863
                        //If it's an html document or a text file
8864
                        if (!$no_display_edit_textarea) {
8865
                            $content = $this->display_document(
8866
                                $extra_info['path'],
8867
                                false,
8868
                                false
8869
                            );
8870
                        }
8871
                    } elseif (is_numeric($extra_info)) {
8872
                        $content = $this->display_document(
8873
                            $extra_info,
8874
                            false,
8875
                            false
8876
                        );
8877
                    } else {
8878
                        $content = '';
8879
                    }
8880
8881
                    if (!$no_display_edit_textarea) {
8882
                        // We need to calculate here some specific settings for the online editor.
8883
                        // The calculated settings work for documents in the Documents tool
8884
                        // (on the root or in subfolders).
8885
                        // For documents in native scorm packages it is unclear whether the
8886
                        // online editor should be activated or not.
8887
8888
                        // A new document, it is in the root of the repository.
8889
                        if (is_array($extra_info) && $extra_info != 'new') {
8890
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8891
                            $relative_path = explode('/', $extra_info['dir']);
8892
                            $cnt = count($relative_path) - 2;
8893
                            if ($cnt < 0) {
8894
                                $cnt = 0;
8895
                            }
8896
                            $relative_prefix = str_repeat('../', $cnt);
8897
                            $relative_path = array_slice($relative_path, 1, $cnt);
8898
                            $relative_path = implode('/', $relative_path);
8899
                            if (strlen($relative_path) > 0) {
8900
                                $relative_path = $relative_path.'/';
8901
                            }
8902
                        } else {
8903
                            $result = $this->generate_lp_folder($_course);
8904
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8905
                            $relative_prefix = '../../';
8906
                        }
8907
8908
                        $editor_config = [
8909
                            'ToolbarSet' => 'LearningPathDocuments',
8910
                            'Width' => '100%',
8911
                            'Height' => '500',
8912
                            'FullPage' => true,
8913
                            'CreateDocumentDir' => $relative_prefix,
8914
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8915
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8916
                        ];
8917
8918
                        if ($_GET['action'] === 'add_item') {
8919
                            $class = 'add';
8920
                            $text = get_lang('LPCreateDocument');
8921
                        } else {
8922
                            if ($_GET['action'] === 'edit_item') {
8923
                                $class = 'save';
8924
                                $text = get_lang('SaveDocument');
8925
                            }
8926
                        }
8927
8928
                        $form->addButtonSave($text, 'submit_button');
8929
                        $renderer = $form->defaultRenderer();
8930
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8931
                        $form->addElement('html', '<div class="editor-lp">');
8932
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8933
                        $form->addElement('html', '</div>');
8934
                        $defaults['content_lp'] = $content;
8935
                    }
8936
                } elseif (is_numeric($extra_info)) {
8937
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8938
8939
                    $return = $this->display_document($extra_info, true, true, true);
8940
                    $form->addElement('html', $return);
8941
                }
8942
            }
8943
        }
8944
        if (isset($extra_info['item_type']) &&
8945
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8946
        ) {
8947
            $parentSelect->freeze();
8948
            $position->freeze();
8949
        }
8950
8951
        if ($action === 'move') {
8952
            $form->addElement('hidden', 'title', $item_title);
8953
            $form->addElement('hidden', 'description', $item_description);
8954
        }
8955
        if (is_numeric($extra_info)) {
8956
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8957
            $form->addElement('hidden', 'path', $extra_info);
8958
        } elseif (is_array($extra_info)) {
8959
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8960
            $form->addElement('hidden', 'path', $extra_info['path']);
8961
        }
8962
8963
        if ($item instanceof learnpathItem) {
8964
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8965
            $form->addElement('hidden', 'path', $item->path);
8966
        }
8967
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8968
        $form->addElement('hidden', 'post_time', time());
8969
        $this->setAuthorLpItem($form);
8970
        $form->setDefaults($defaults);
8971
8972
        return $form->returnForm();
8973
    }
8974
8975
    /**
8976
     * Returns the form to update or create a read-out text.
8977
     *
8978
     * @param string $action     "add" or "edit"
8979
     * @param int    $id         ID of the lp_item (if already exists)
8980
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8981
     *
8982
     * @throws Exception
8983
     * @throws HTML_QuickForm_Error
8984
     *
8985
     * @return string HTML form
8986
     */
8987
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8988
    {
8989
        $course_id = api_get_course_int_id();
8990
        $_course = api_get_course_info();
8991
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8992
8993
        $no_display_edit_textarea = false;
8994
        //If action==edit document
8995
        //We don't display the document form if it's not an editable document (html or txt file)
8996
        if ($action == 'edit') {
8997
            if (is_array($extra_info)) {
8998
                $path_parts = pathinfo($extra_info['dir']);
8999
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9000
                    $no_display_edit_textarea = true;
9001
                }
9002
            }
9003
        }
9004
        $no_display_add = false;
9005
9006
        $item_title = '';
9007
        $item_description = '';
9008
        if ($id != 0 && is_array($extra_info)) {
9009
            $item_title = stripslashes($extra_info['title']);
9010
            $item_description = stripslashes($extra_info['description']);
9011
            $item_terms = stripslashes($extra_info['terms']);
9012
            if (empty($item_title)) {
9013
                $path_parts = pathinfo($extra_info['path']);
9014
                $item_title = stripslashes($path_parts['filename']);
9015
            }
9016
        } elseif (is_numeric($extra_info)) {
9017
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9018
            $result = Database::query($sql);
9019
            $row = Database::fetch_array($result);
9020
            $item_title = $row['title'];
9021
            $item_title = str_replace('_', ' ', $item_title);
9022
            if (empty($item_title)) {
9023
                $path_parts = pathinfo($row['path']);
9024
                $item_title = stripslashes($path_parts['filename']);
9025
            }
9026
        }
9027
9028
        $parent = 0;
9029
        if ($id != 0 && is_array($extra_info)) {
9030
            $parent = $extra_info['parent_item_id'];
9031
        }
9032
9033
        $arrLP = $this->getItemsForForm();
9034
        $this->tree_array($arrLP);
9035
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9036
        unset($this->arrMenu);
9037
9038
        if ($action === 'add') {
9039
            $formHeader = get_lang('CreateTheDocument');
9040
        } else {
9041
            $formHeader = get_lang('EditTheCurrentDocument');
9042
        }
9043
9044
        if ('edit' === $action) {
9045
            $urlAudioIcon = Display::url(
9046
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9047
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9048
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9049
            );
9050
        } else {
9051
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9052
        }
9053
9054
        $form = new FormValidator(
9055
            'frm_add_reading',
9056
            'POST',
9057
            $this->getCurrentBuildingModeURL(),
9058
            '',
9059
            ['enctype' => 'multipart/form-data']
9060
        );
9061
        $form->addHeader($formHeader);
9062
        $form->addHtml(
9063
            Display::return_message(
9064
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9065
                'normal',
9066
                false
9067
            )
9068
        );
9069
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9070
        $defaults['description'] = $item_description;
9071
9072
        $data = $this->generate_lp_folder($_course);
9073
9074
        if ($action != 'edit') {
9075
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9076
            DocumentManager::build_directory_selector(
9077
                $folders,
9078
                '',
9079
                [],
9080
                true,
9081
                $form,
9082
                'directory_parent_id'
9083
            );
9084
        }
9085
9086
        if (isset($data['id'])) {
9087
            $defaults['directory_parent_id'] = $data['id'];
9088
        }
9089
        $this->setItemTitle($form);
9090
9091
        $arrHide[0]['value'] = $this->name;
9092
        $arrHide[0]['padding'] = 20;
9093
9094
        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...
9095
            if ($action != 'add') {
9096
                if ($arrLP[$i]['item_type'] == 'dir' &&
9097
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9098
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9099
                ) {
9100
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9101
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9102
                }
9103
            } else {
9104
                if ($arrLP[$i]['item_type'] == 'dir') {
9105
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9106
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9107
                }
9108
            }
9109
        }
9110
9111
        $parent_select = $form->addSelect(
9112
            'parent',
9113
            get_lang('Parent'),
9114
            [],
9115
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9116
        );
9117
9118
        $my_count = 0;
9119
        foreach ($arrHide as $key => $value) {
9120
            if ($my_count != 0) {
9121
                // The LP name is also the first section and is not in the same charset like the other sections.
9122
                $value['value'] = Security::remove_XSS($value['value']);
9123
                $parent_select->addOption(
9124
                    $value['value'],
9125
                    $key,
9126
                    'style="padding-left:'.$value['padding'].'px;"'
9127
                );
9128
            } else {
9129
                $value['value'] = Security::remove_XSS($value['value']);
9130
                $parent_select->addOption(
9131
                    $value['value'],
9132
                    $key,
9133
                    'style="padding-left:'.$value['padding'].'px;"'
9134
                );
9135
            }
9136
            $my_count++;
9137
        }
9138
9139
        if (!empty($id)) {
9140
            $parent_select->setSelected($parent);
9141
        } else {
9142
            $parent_item_id = Session::read('parent_item_id', 0);
9143
            $parent_select->setSelected($parent_item_id);
9144
        }
9145
9146
        if (is_array($arrLP)) {
9147
            reset($arrLP);
9148
        }
9149
9150
        $arrHide = [];
9151
        $s_selected_position = null;
9152
9153
        // POSITION
9154
        $lastPosition = null;
9155
9156
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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