Passed
Push — 1.11.x ( 3ffbf5...fe2b1d )
by Julito
09:53
created

learnpath   F

Complexity

Total Complexity 1924

Size/Duplication

Total Lines 14166
Duplicated Lines 0 %

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 7848
c 8
b 0
f 0
dl 0
loc 14166
rs 0.8
wmc 1924

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

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
            if (!empty($row['category_id'])) {
2347
                $category = self::getCategory($row['category_id']);
2348
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2349
                    return false;
2350
                }
2351
            }
2352
2353
            $prerequisite = $row['prerequisite'];
2354
            $is_visible = true;
2355
2356
            $isBlocked = self::isBlockedByPrerequisite(
2357
                $student_id,
2358
                $prerequisite,
2359
                $courseInfo,
2360
                $sessionId
2361
            );
2362
2363
            if ($isBlocked) {
2364
                $is_visible = false;
2365
            }
2366
2367
            // Also check the time availability of the LP
2368
            if ($is_visible) {
2369
                // Adding visibility restrictions
2370
                if (!empty($row['publicated_on'])) {
2371
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2372
                        $is_visible = false;
2373
                    }
2374
                }
2375
                // Blocking empty start times see BT#2800
2376
                global $_custom;
2377
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2378
                    $_custom['lps_hidden_when_no_start_date']
2379
                ) {
2380
                    if (empty($row['publicated_on'])) {
2381
                        $is_visible = false;
2382
                    }
2383
                }
2384
2385
                if (!empty($row['expired_on'])) {
2386
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2387
                        $is_visible = false;
2388
                    }
2389
                }
2390
            }
2391
2392
            if ($is_visible) {
2393
                $subscriptionSettings = self::getSubscriptionSettings();
2394
2395
                // Check if the subscription users/group to a LP is ON
2396
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2397
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2398
                ) {
2399
                    // Try group
2400
                    $is_visible = false;
2401
                    // Checking only the user visibility
2402
                    $userVisibility = api_get_item_visibility(
2403
                        $courseInfo,
2404
                        'learnpath',
2405
                        $row['id'],
2406
                        $sessionId,
2407
                        $student_id,
2408
                        'LearnpathSubscription'
2409
                    );
2410
2411
                    if ($userVisibility == 1) {
2412
                        $is_visible = true;
2413
                    } else {
2414
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2415
                        if (!empty($userGroups)) {
2416
                            foreach ($userGroups as $groupInfo) {
2417
                                $groupId = $groupInfo['iid'];
2418
                                $userVisibility = api_get_item_visibility(
2419
                                    $courseInfo,
2420
                                    'learnpath',
2421
                                    $row['id'],
2422
                                    $sessionId,
2423
                                    null,
2424
                                    'LearnpathSubscription',
2425
                                    $groupId
2426
                                );
2427
2428
                                if ($userVisibility == 1) {
2429
                                    $is_visible = true;
2430
                                    break;
2431
                                }
2432
                            }
2433
                        }
2434
                    }
2435
                }
2436
            }
2437
2438
            return $is_visible;
2439
        }
2440
2441
        return false;
2442
    }
2443
2444
    /**
2445
     * @param int $lpId
2446
     * @param int $userId
2447
     * @param int $courseId
2448
     * @param int $sessionId
2449
     *
2450
     * @return int
2451
     */
2452
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2453
    {
2454
        $lpId = (int) $lpId;
2455
        $userId = (int) $userId;
2456
        $courseId = (int) $courseId;
2457
        $sessionId = (int) $sessionId;
2458
2459
        $sessionCondition = api_get_session_condition($sessionId);
2460
        $table = Database::get_course_table(TABLE_LP_VIEW);
2461
        $sql = "SELECT progress FROM $table
2462
                WHERE
2463
                    c_id = $courseId AND
2464
                    lp_id = $lpId AND
2465
                    user_id = $userId $sessionCondition ";
2466
        $res = Database::query($sql);
2467
2468
        $progress = 0;
2469
        if (Database::num_rows($res) > 0) {
2470
            $row = Database::fetch_array($res);
2471
            $progress = (int) $row['progress'];
2472
        }
2473
2474
        return $progress;
2475
    }
2476
2477
    /**
2478
     * @param array $lpList
2479
     * @param int   $userId
2480
     * @param int   $courseId
2481
     * @param int   $sessionId
2482
     *
2483
     * @return array
2484
     */
2485
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2486
    {
2487
        $lpList = array_map('intval', $lpList);
2488
        if (empty($lpList)) {
2489
            return [];
2490
        }
2491
2492
        $lpList = implode("','", $lpList);
2493
2494
        $userId = (int) $userId;
2495
        $courseId = (int) $courseId;
2496
        $sessionId = (int) $sessionId;
2497
2498
        $sessionCondition = api_get_session_condition($sessionId);
2499
        $table = Database::get_course_table(TABLE_LP_VIEW);
2500
        $sql = "SELECT lp_id, progress FROM $table
2501
                WHERE
2502
                    c_id = $courseId AND
2503
                    lp_id IN ('".$lpList."') AND
2504
                    user_id = $userId $sessionCondition ";
2505
        $res = Database::query($sql);
2506
2507
        if (Database::num_rows($res) > 0) {
2508
            $list = [];
2509
            while ($row = Database::fetch_array($res)) {
2510
                $list[$row['lp_id']] = $row['progress'];
2511
            }
2512
2513
            return $list;
2514
        }
2515
2516
        return [];
2517
    }
2518
2519
    /**
2520
     * Displays a progress bar
2521
     * completed so far.
2522
     *
2523
     * @param int    $percentage Progress value to display
2524
     * @param string $text_add   Text to display near the progress value
2525
     *
2526
     * @return string HTML string containing the progress bar
2527
     */
2528
    public static function get_progress_bar($percentage = -1, $text_add = '')
2529
    {
2530
        $text = $percentage.$text_add;
2531
        $output = '<div class="progress">
2532
            <div id="progress_bar_value"
2533
                class="progress-bar progress-bar-success" role="progressbar"
2534
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2535
            '.$text.'
2536
            </div>
2537
        </div>';
2538
2539
        return $output;
2540
    }
2541
2542
    /**
2543
     * @param string $mode can be '%' or 'abs'
2544
     *                     otherwise this value will be used $this->progress_bar_mode
2545
     *
2546
     * @return string
2547
     */
2548
    public function getProgressBar($mode = null)
2549
    {
2550
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2551
2552
        return self::get_progress_bar($percentage, $text_add);
2553
    }
2554
2555
    /**
2556
     * Gets the progress bar info to display inside the progress bar.
2557
     * Also used by scorm_api.php.
2558
     *
2559
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2560
     *                     we display a number of completed elements per total elements
2561
     * @param int    $add  Additional steps to fake as completed
2562
     *
2563
     * @return array Percentage or number and symbol (% or /xx)
2564
     */
2565
    public function get_progress_bar_text($mode = '', $add = 0)
2566
    {
2567
        if (empty($mode)) {
2568
            $mode = $this->progress_bar_mode;
2569
        }
2570
        $text = '';
2571
        $percentage = 0;
2572
        // If the option to use the score as progress is set for this learning
2573
        // path, then the rules are completely different: we assume only one
2574
        // item exists and the progress of the LP depends on the score
2575
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2576
        if ($scoreAsProgressSetting === true) {
2577
            $scoreAsProgress = $this->getUseScoreAsProgress();
2578
            if ($scoreAsProgress) {
2579
                // Get single item's score
2580
                $itemId = $this->get_current_item_id();
2581
                $item = $this->getItem($itemId);
2582
                $score = $item->get_score();
2583
                $maxScore = $item->get_max();
2584
                if ($mode = '%') {
2585
                    if (!empty($maxScore)) {
2586
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2587
                    }
2588
                    $percentage = number_format($percentage, 0);
2589
                    $text = '%';
2590
                } else {
2591
                    $percentage = $score;
2592
                    $text = '/'.$maxScore;
2593
                }
2594
2595
                return [$percentage, $text];
2596
            }
2597
        }
2598
        // otherwise just continue the normal processing of progress
2599
        $total_items = $this->getTotalItemsCountWithoutDirs();
2600
        $completeItems = $this->get_complete_items_count();
2601
        if ($add != 0) {
2602
            $completeItems += $add;
2603
        }
2604
        if ($completeItems > $total_items) {
2605
            $completeItems = $total_items;
2606
        }
2607
        if ($mode == '%') {
2608
            if ($total_items > 0) {
2609
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2610
            }
2611
            $percentage = number_format($percentage, 0);
2612
            $text = '%';
2613
        } elseif ($mode === 'abs') {
2614
            $percentage = $completeItems;
2615
            $text = '/'.$total_items;
2616
        }
2617
2618
        return [
2619
            $percentage,
2620
            $text,
2621
        ];
2622
    }
2623
2624
    /**
2625
     * Gets the progress bar mode.
2626
     *
2627
     * @return string The progress bar mode attribute
2628
     */
2629
    public function get_progress_bar_mode()
2630
    {
2631
        if (!empty($this->progress_bar_mode)) {
2632
            return $this->progress_bar_mode;
2633
        }
2634
2635
        return '%';
2636
    }
2637
2638
    /**
2639
     * Gets the learnpath theme (remote or local).
2640
     *
2641
     * @return string Learnpath theme
2642
     */
2643
    public function get_theme()
2644
    {
2645
        if (!empty($this->theme)) {
2646
            return $this->theme;
2647
        }
2648
2649
        return '';
2650
    }
2651
2652
    /**
2653
     * Gets the learnpath session id.
2654
     *
2655
     * @return int
2656
     */
2657
    public function get_lp_session_id()
2658
    {
2659
        if (!empty($this->lp_session_id)) {
2660
            return (int) $this->lp_session_id;
2661
        }
2662
2663
        return 0;
2664
    }
2665
2666
    /**
2667
     * Gets the learnpath image.
2668
     *
2669
     * @return string Web URL of the LP image
2670
     */
2671
    public function get_preview_image()
2672
    {
2673
        if (!empty($this->preview_image)) {
2674
            return $this->preview_image;
2675
        }
2676
2677
        return '';
2678
    }
2679
2680
    /**
2681
     * @param string $size
2682
     * @param string $path_type
2683
     *
2684
     * @return bool|string
2685
     */
2686
    public function get_preview_image_path($size = null, $path_type = 'web')
2687
    {
2688
        $preview_image = $this->get_preview_image();
2689
        if (isset($preview_image) && !empty($preview_image)) {
2690
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2691
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2692
2693
            if (isset($size)) {
2694
                $info = pathinfo($preview_image);
2695
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2696
2697
                if (file_exists($image_sys_path.$image_custom_size)) {
2698
                    if ($path_type == 'web') {
2699
                        return $image_path.$image_custom_size;
2700
                    } else {
2701
                        return $image_sys_path.$image_custom_size;
2702
                    }
2703
                }
2704
            } else {
2705
                if ($path_type == 'web') {
2706
                    return $image_path.$preview_image;
2707
                } else {
2708
                    return $image_sys_path.$preview_image;
2709
                }
2710
            }
2711
        }
2712
2713
        return false;
2714
    }
2715
2716
    /**
2717
     * Gets the learnpath author.
2718
     *
2719
     * @return string LP's author
2720
     */
2721
    public function get_author()
2722
    {
2723
        if (!empty($this->author)) {
2724
            return $this->author;
2725
        }
2726
2727
        return '';
2728
    }
2729
2730
    /**
2731
     * Gets hide table of contents.
2732
     *
2733
     * @return int
2734
     */
2735
    public function getHideTableOfContents()
2736
    {
2737
        return (int) $this->hide_toc_frame;
2738
    }
2739
2740
    /**
2741
     * Generate a new prerequisites string for a given item. If this item was a sco and
2742
     * its prerequisites were strings (instead of IDs), then transform those strings into
2743
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2744
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2745
     * same rule as the scormExport() method.
2746
     *
2747
     * @param int $item_id Item ID
2748
     *
2749
     * @return string Prerequisites string ready for the export as SCORM
2750
     */
2751
    public function get_scorm_prereq_string($item_id)
2752
    {
2753
        if ($this->debug > 0) {
2754
            error_log('In learnpath::get_scorm_prereq_string()');
2755
        }
2756
        if (!is_object($this->items[$item_id])) {
2757
            return false;
2758
        }
2759
        /** @var learnpathItem $oItem */
2760
        $oItem = $this->items[$item_id];
2761
        $prereq = $oItem->get_prereq_string();
2762
2763
        if (empty($prereq)) {
2764
            return '';
2765
        }
2766
        if (preg_match('/^\d+$/', $prereq) &&
2767
            isset($this->items[$prereq]) &&
2768
            is_object($this->items[$prereq])
2769
        ) {
2770
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2771
            // then simply return it (with the ITEM_ prefix).
2772
            //return 'ITEM_' . $prereq;
2773
            return $this->items[$prereq]->ref;
2774
        } else {
2775
            if (isset($this->refs_list[$prereq])) {
2776
                // It's a simple string item from which the ID can be found in the refs list,
2777
                // so we can transform it directly to an ID for export.
2778
                return $this->items[$this->refs_list[$prereq]]->ref;
2779
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2780
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2781
            } else {
2782
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2783
                // and replace them, one by one, by the internal IDs (chamilo db)
2784
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2785
                // by a space as well.
2786
                $find = [
2787
                    '&',
2788
                    '|',
2789
                    '~',
2790
                    '=',
2791
                    '<>',
2792
                    '{',
2793
                    '}',
2794
                    '*',
2795
                    '(',
2796
                    ')',
2797
                ];
2798
                $replace = [
2799
                    ' ',
2800
                    ' ',
2801
                    ' ',
2802
                    ' ',
2803
                    ' ',
2804
                    ' ',
2805
                    ' ',
2806
                    ' ',
2807
                    ' ',
2808
                    ' ',
2809
                ];
2810
                $prereq_mod = str_replace($find, $replace, $prereq);
2811
                $ids = explode(' ', $prereq_mod);
2812
                foreach ($ids as $id) {
2813
                    $id = trim($id);
2814
                    if (isset($this->refs_list[$id])) {
2815
                        $prereq = preg_replace(
2816
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2817
                            'ITEM_'.$this->refs_list[$id],
2818
                            $prereq
2819
                        );
2820
                    }
2821
                }
2822
2823
                return $prereq;
2824
            }
2825
        }
2826
    }
2827
2828
    /**
2829
     * Returns the XML DOM document's node.
2830
     *
2831
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2832
     * @param string   $id       The identifier to look for
2833
     *
2834
     * @return mixed The reference to the element found with that identifier. False if not found
2835
     */
2836
    public function get_scorm_xml_node(&$children, $id)
2837
    {
2838
        for ($i = 0; $i < $children->length; $i++) {
2839
            $item_temp = $children->item($i);
2840
            if ($item_temp->nodeName == 'item') {
2841
                if ($item_temp->getAttribute('identifier') == $id) {
2842
                    return $item_temp;
2843
                }
2844
            }
2845
            $subchildren = $item_temp->childNodes;
2846
            if ($subchildren && $subchildren->length > 0) {
2847
                $val = $this->get_scorm_xml_node($subchildren, $id);
2848
                if (is_object($val)) {
2849
                    return $val;
2850
                }
2851
            }
2852
        }
2853
2854
        return false;
2855
    }
2856
2857
    /**
2858
     * Gets the status list for all LP's items.
2859
     *
2860
     * @return array Array of [index] => [item ID => current status]
2861
     */
2862
    public function get_items_status_list()
2863
    {
2864
        $list = [];
2865
        foreach ($this->ordered_items as $item_id) {
2866
            $list[] = [
2867
                $item_id => $this->items[$item_id]->get_status(),
2868
            ];
2869
        }
2870
2871
        return $list;
2872
    }
2873
2874
    /**
2875
     * Return the number of interactions for the given learnpath Item View ID.
2876
     * This method can be used as static.
2877
     *
2878
     * @param int $lp_iv_id  Item View ID
2879
     * @param int $course_id course id
2880
     *
2881
     * @return int
2882
     */
2883
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2884
    {
2885
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2886
        $lp_iv_id = (int) $lp_iv_id;
2887
        $course_id = (int) $course_id;
2888
2889
        $sql = "SELECT count(*) FROM $table
2890
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2891
        $res = Database::query($sql);
2892
        $num = 0;
2893
        if (Database::num_rows($res)) {
2894
            $row = Database::fetch_array($res);
2895
            $num = $row[0];
2896
        }
2897
2898
        return $num;
2899
    }
2900
2901
    /**
2902
     * Return the interactions as an array for the given lp_iv_id.
2903
     * This method can be used as static.
2904
     *
2905
     * @param int $lp_iv_id Learnpath Item View ID
2906
     *
2907
     * @return array
2908
     *
2909
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2910
     */
2911
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2912
    {
2913
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2914
        $list = [];
2915
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2916
        $lp_iv_id = (int) $lp_iv_id;
2917
2918
        if (empty($lp_iv_id) || empty($course_id)) {
2919
            return [];
2920
        }
2921
2922
        $sql = "SELECT * FROM $table
2923
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2924
                ORDER BY order_id ASC";
2925
        $res = Database::query($sql);
2926
        $num = Database::num_rows($res);
2927
        if ($num > 0) {
2928
            $list[] = [
2929
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2930
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2931
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2932
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2933
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2934
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2935
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2936
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2937
                'student_response_formatted' => '',
2938
            ];
2939
            while ($row = Database::fetch_array($res)) {
2940
                $studentResponseFormatted = urldecode($row['student_response']);
2941
                $content_student_response = explode('__|', $studentResponseFormatted);
2942
                if (count($content_student_response) > 0) {
2943
                    if (count($content_student_response) >= 3) {
2944
                        // Pop the element off the end of array.
2945
                        array_pop($content_student_response);
2946
                    }
2947
                    $studentResponseFormatted = implode(',', $content_student_response);
2948
                }
2949
2950
                $list[] = [
2951
                    'order_id' => $row['order_id'] + 1,
2952
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2953
                    'type' => $row['interaction_type'],
2954
                    'time' => $row['completion_time'],
2955
                    'correct_responses' => '', // Hide correct responses from students.
2956
                    'student_response' => $row['student_response'],
2957
                    'result' => $row['result'],
2958
                    'latency' => $row['latency'],
2959
                    'student_response_formatted' => $studentResponseFormatted,
2960
                ];
2961
            }
2962
        }
2963
2964
        return $list;
2965
    }
2966
2967
    /**
2968
     * Return the number of objectives for the given learnpath Item View ID.
2969
     * This method can be used as static.
2970
     *
2971
     * @param int $lp_iv_id  Item View ID
2972
     * @param int $course_id Course ID
2973
     *
2974
     * @return int Number of objectives
2975
     */
2976
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2977
    {
2978
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2979
        $course_id = (int) $course_id;
2980
        $lp_iv_id = (int) $lp_iv_id;
2981
        $sql = "SELECT count(*) FROM $table
2982
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2983
        //@todo seems that this always returns 0
2984
        $res = Database::query($sql);
2985
        $num = 0;
2986
        if (Database::num_rows($res)) {
2987
            $row = Database::fetch_array($res);
2988
            $num = $row[0];
2989
        }
2990
2991
        return $num;
2992
    }
2993
2994
    /**
2995
     * Return the objectives as an array for the given lp_iv_id.
2996
     * This method can be used as static.
2997
     *
2998
     * @param int $lpItemViewId Learnpath Item View ID
2999
     * @param int $course_id
3000
     *
3001
     * @return array
3002
     *
3003
     * @todo    Translate labels
3004
     */
3005
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
3006
    {
3007
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
3008
        $lpItemViewId = (int) $lpItemViewId;
3009
3010
        if (empty($course_id) || empty($lpItemViewId)) {
3011
            return [];
3012
        }
3013
3014
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
3015
        $sql = "SELECT * FROM $table
3016
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
3017
                ORDER BY order_id ASC";
3018
        $res = Database::query($sql);
3019
        $num = Database::num_rows($res);
3020
        $list = [];
3021
        if ($num > 0) {
3022
            $list[] = [
3023
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3024
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3025
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3026
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3027
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3028
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3029
            ];
3030
            while ($row = Database::fetch_array($res)) {
3031
                $list[] = [
3032
                    'order_id' => $row['order_id'] + 1,
3033
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3034
                    'score_raw' => $row['score_raw'],
3035
                    'score_max' => $row['score_max'],
3036
                    'score_min' => $row['score_min'],
3037
                    'status' => $row['status'],
3038
                ];
3039
            }
3040
        }
3041
3042
        return $list;
3043
    }
3044
3045
    /**
3046
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3047
     * used by get_html_toc() to be ready to display.
3048
     *
3049
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3050
     */
3051
    public function get_toc()
3052
    {
3053
        $toc = [];
3054
        foreach ($this->ordered_items as $item_id) {
3055
            // TODO: Change this link generation and use new function instead.
3056
            $toc[] = [
3057
                'id' => $item_id,
3058
                'title' => $this->items[$item_id]->get_title(),
3059
                'status' => $this->items[$item_id]->get_status(),
3060
                'level' => $this->items[$item_id]->get_level(),
3061
                'type' => $this->items[$item_id]->get_type(),
3062
                'description' => $this->items[$item_id]->get_description(),
3063
                'path' => $this->items[$item_id]->get_path(),
3064
                'parent' => $this->items[$item_id]->get_parent(),
3065
            ];
3066
        }
3067
3068
        return $toc;
3069
    }
3070
3071
    /**
3072
     * Returns the CSS class name associated with a given item status.
3073
     *
3074
     * @param $status string an item status
3075
     *
3076
     * @return string CSS class name
3077
     */
3078
    public static function getStatusCSSClassName($status)
3079
    {
3080
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3081
            return self::STATUS_CSS_CLASS_NAME[$status];
3082
        }
3083
3084
        return '';
3085
    }
3086
3087
    /**
3088
     * Generate the tree of contents for this learnpath as an associative array tree
3089
     * with keys id, title, status, type, description, path, parent_id, children
3090
     * (title and descriptions as secured)
3091
     * and clues for CSS class composition:
3092
     *  - booleans is_current, is_parent_of_current, is_chapter
3093
     *  - string status_css_class_name.
3094
     *
3095
     * @param $parentId int restrict returned list to children of this parent
3096
     *
3097
     * @return array TOC as a table
3098
     */
3099
    public function getTOCTree($parentId = 0)
3100
    {
3101
        $toc = [];
3102
        $currentItemId = $this->get_current_item_id();
3103
3104
        foreach ($this->ordered_items as $itemId) {
3105
            $item = $this->items[$itemId];
3106
            if ($item->get_parent() == $parentId) {
3107
                $title = $item->get_title();
3108
                if (empty($title)) {
3109
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3110
                }
3111
3112
                $itemData = [
3113
                    'id' => $itemId,
3114
                    'title' => Security::remove_XSS($title),
3115
                    'status' => $item->get_status(),
3116
                    'level' => $item->get_level(), // FIXME should not be needed
3117
                    'type' => $item->get_type(),
3118
                    'description' => Security::remove_XSS($item->get_description()),
3119
                    'path' => $item->get_path(),
3120
                    'parent_id' => $item->get_parent(),
3121
                    'children' => $this->getTOCTree($itemId),
3122
                    'is_current' => ($itemId == $currentItemId),
3123
                    'is_parent_of_current' => false,
3124
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3125
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3126
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3127
                ];
3128
3129
                if (!empty($itemData['children'])) {
3130
                    foreach ($itemData['children'] as $child) {
3131
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3132
                            $itemData['is_parent_of_current'] = true;
3133
                            break;
3134
                        }
3135
                    }
3136
                }
3137
3138
                $toc[] = $itemData;
3139
            }
3140
        }
3141
3142
        return $toc;
3143
    }
3144
3145
    /**
3146
     * Generate and return the table of contents for this learnpath. The JS
3147
     * table returned is used inside of scorm_api.php.
3148
     *
3149
     * @param string $varname
3150
     *
3151
     * @return string A JS array variable construction
3152
     */
3153
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3154
    {
3155
        $toc = $varname.' = new Array();';
3156
        foreach ($this->ordered_items as $item_id) {
3157
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3158
        }
3159
3160
        return $toc;
3161
    }
3162
3163
    /**
3164
     * Gets the learning path type.
3165
     *
3166
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3167
     *
3168
     * @return mixed Type ID or name, depending on the parameter
3169
     */
3170
    public function get_type($get_name = false)
3171
    {
3172
        $res = false;
3173
        if (!empty($this->type) && (!$get_name)) {
3174
            $res = $this->type;
3175
        }
3176
3177
        return $res;
3178
    }
3179
3180
    /**
3181
     * Gets the learning path type as static method.
3182
     *
3183
     * @param int $lp_id
3184
     *
3185
     * @return mixed Returns the lp_type: 1 = Chamilo lms / 2 = SCORM
3186
     */
3187
    public static function get_type_static($lp_id = 0)
3188
    {
3189
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3190
        $lp_id = (int) $lp_id;
3191
        $sql = "SELECT lp_type FROM $tbl_lp
3192
                WHERE iid = $lp_id";
3193
        $res = Database::query($sql);
3194
        if ($res === false) {
3195
            return null;
3196
        }
3197
        if (Database::num_rows($res) <= 0) {
3198
            return null;
3199
        }
3200
        $row = Database::fetch_array($res);
3201
3202
        return $row['lp_type'];
3203
    }
3204
3205
    /**
3206
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3207
     * This method can be used as abstract and is recursive.
3208
     *
3209
     * @param int $lp        Learnpath ID
3210
     * @param int $parent    Parent ID of the items to look for
3211
     * @param int $course_id
3212
     *
3213
     * @return array Ordered list of item IDs (empty array on error)
3214
     */
3215
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3216
    {
3217
        if (empty($course_id)) {
3218
            $course_id = api_get_course_int_id();
3219
        } else {
3220
            $course_id = (int) $course_id;
3221
        }
3222
        $list = [];
3223
3224
        if (empty($lp)) {
3225
            return $list;
3226
        }
3227
3228
        $lp = (int) $lp;
3229
        $parent = (int) $parent;
3230
3231
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3232
        $sql = "SELECT iid FROM $tbl_lp_item
3233
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3234
                ORDER BY display_order";
3235
3236
        $res = Database::query($sql);
3237
        while ($row = Database::fetch_array($res)) {
3238
            $sublist = self::get_flat_ordered_items_list(
3239
                $lp,
3240
                $row['iid'],
3241
                $course_id
3242
            );
3243
            $list[] = $row['iid'];
3244
            foreach ($sublist as $item) {
3245
                $list[] = $item;
3246
            }
3247
        }
3248
3249
        return $list;
3250
    }
3251
3252
    /**
3253
     * @return array
3254
     */
3255
    public static function getChapterTypes()
3256
    {
3257
        return [
3258
            'dir',
3259
        ];
3260
    }
3261
3262
    /**
3263
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3264
     *
3265
     * @param $tree
3266
     *
3267
     * @return array HTML TOC ready to display
3268
     */
3269
    public function getParentToc($tree)
3270
    {
3271
        if (empty($tree)) {
3272
            $tree = $this->get_toc();
3273
        }
3274
        $dirTypes = self::getChapterTypes();
3275
        $myCurrentId = $this->get_current_item_id();
3276
        $listParent = [];
3277
        $listChildren = [];
3278
        $listNotParent = [];
3279
        $list = [];
3280
        foreach ($tree as $subtree) {
3281
            if (in_array($subtree['type'], $dirTypes)) {
3282
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3283
                $subtree['children'] = $listChildren;
3284
                if (!empty($subtree['children'])) {
3285
                    foreach ($subtree['children'] as $subItem) {
3286
                        if ($subItem['id'] == $this->current) {
3287
                            $subtree['parent_current'] = 'in';
3288
                            $subtree['current'] = 'on';
3289
                        }
3290
                    }
3291
                }
3292
                $listParent[] = $subtree;
3293
            }
3294
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3295
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3296
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3297
                }
3298
3299
                $title = Security::remove_XSS($subtree['title']);
3300
                unset($subtree['title']);
3301
3302
                if (empty($title)) {
3303
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3304
                }
3305
                $classStyle = null;
3306
                if ($subtree['id'] == $this->current) {
3307
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3308
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3309
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3310
                }
3311
                $subtree['title'] = $title;
3312
                $subtree['class'] = $classStyle.' '.$cssStatus;
3313
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3314
                $subtree['current_id'] = $myCurrentId;
3315
                $listNotParent[] = $subtree;
3316
            }
3317
        }
3318
3319
        $list['are_parents'] = $listParent;
3320
        $list['not_parents'] = $listNotParent;
3321
3322
        return $list;
3323
    }
3324
3325
    /**
3326
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3327
     *
3328
     * @param array $tree
3329
     * @param int   $id
3330
     * @param bool  $parent
3331
     *
3332
     * @return array HTML TOC ready to display
3333
     */
3334
    public function getChildrenToc($tree, $id, $parent = true)
3335
    {
3336
        if (empty($tree)) {
3337
            $tree = $this->get_toc();
3338
        }
3339
3340
        $dirTypes = self::getChapterTypes();
3341
        $currentItemId = $this->get_current_item_id();
3342
        $list = [];
3343
3344
        foreach ($tree as $subtree) {
3345
            $subtree['tree'] = null;
3346
3347
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3348
                if ($subtree['id'] == $this->current) {
3349
                    $subtree['current'] = 'active';
3350
                } else {
3351
                    $subtree['current'] = null;
3352
                }
3353
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3354
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3355
                }
3356
3357
                $title = Security::remove_XSS($subtree['title']);
3358
                unset($subtree['title']);
3359
                if (empty($title)) {
3360
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3361
                }
3362
3363
                $classStyle = null;
3364
                if ($subtree['id'] == $this->current) {
3365
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3366
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3367
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3368
                }
3369
3370
                if (in_array($subtree['type'], $dirTypes)) {
3371
                    $subtree['title'] = stripslashes($title);
3372
                } else {
3373
                    $subtree['title'] = $title;
3374
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3375
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3376
                    $subtree['current_id'] = $currentItemId;
3377
                }
3378
                $list[] = $subtree;
3379
            }
3380
        }
3381
3382
        return $list;
3383
    }
3384
3385
    /**
3386
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3387
     *
3388
     * @param array $toc_list
3389
     *
3390
     * @return array HTML TOC ready to display
3391
     */
3392
    public function getListArrayToc($toc_list = [])
3393
    {
3394
        if (empty($toc_list)) {
3395
            $toc_list = $this->get_toc();
3396
        }
3397
        // Temporary variables.
3398
        $currentItemId = $this->get_current_item_id();
3399
        $list = [];
3400
        $arrayList = [];
3401
3402
        foreach ($toc_list as $item) {
3403
            $list['id'] = $item['id'];
3404
            $list['status'] = $item['status'];
3405
            $cssStatus = null;
3406
3407
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3408
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3409
            }
3410
3411
            $classStyle = ' ';
3412
            $dirTypes = self::getChapterTypes();
3413
3414
            if (in_array($item['type'], $dirTypes)) {
3415
                $classStyle = 'scorm_item_section ';
3416
            }
3417
            if ($item['id'] == $this->current) {
3418
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3419
            } elseif (!in_array($item['type'], $dirTypes)) {
3420
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3421
            }
3422
            $title = $item['title'];
3423
            if (empty($title)) {
3424
                $title = self::rl_get_resource_name(
3425
                    api_get_course_id(),
3426
                    $this->get_id(),
3427
                    $item['id']
3428
                );
3429
            }
3430
            $title = Security::remove_XSS($item['title']);
3431
3432
            if (empty($item['description'])) {
3433
                $list['description'] = $title;
3434
            } else {
3435
                $list['description'] = $item['description'];
3436
            }
3437
3438
            $list['class'] = $classStyle.' '.$cssStatus;
3439
            $list['level'] = $item['level'];
3440
            $list['type'] = $item['type'];
3441
3442
            if (in_array($item['type'], $dirTypes)) {
3443
                $list['css_level'] = 'level_'.$item['level'];
3444
            } else {
3445
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3446
            }
3447
3448
            if (in_array($item['type'], $dirTypes)) {
3449
                $list['title'] = stripslashes($title);
3450
            } else {
3451
                $list['title'] = stripslashes($title);
3452
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3453
                $list['current_id'] = $currentItemId;
3454
            }
3455
            $arrayList[] = $list;
3456
        }
3457
3458
        return $arrayList;
3459
    }
3460
3461
    /**
3462
     * Returns an HTML-formatted string ready to display with teacher buttons
3463
     * in LP view menu.
3464
     *
3465
     * @return string HTML TOC ready to display
3466
     */
3467
    public function get_teacher_toc_buttons()
3468
    {
3469
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3470
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3471
        $html = '';
3472
        if ($isAllow && $hideIcons == false) {
3473
            if ($this->get_lp_session_id() == api_get_session_id()) {
3474
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3475
                $html .= '<div class="btn-group">';
3476
                $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'>".
3477
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3478
                $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'>".
3479
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3480
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3481
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3482
                $html .= '</div>';
3483
                $html .= '</div>';
3484
            }
3485
        }
3486
3487
        return $html;
3488
    }
3489
3490
    /**
3491
     * Gets the learnpath maker name - generally the editor's name.
3492
     *
3493
     * @return string Learnpath maker name
3494
     */
3495
    public function get_maker()
3496
    {
3497
        if (!empty($this->maker)) {
3498
            return $this->maker;
3499
        }
3500
3501
        return '';
3502
    }
3503
3504
    /**
3505
     * Gets the learnpath name/title.
3506
     *
3507
     * @return string Learnpath name/title
3508
     */
3509
    public function get_name()
3510
    {
3511
        if (!empty($this->name)) {
3512
            return $this->name;
3513
        }
3514
3515
        return 'N/A';
3516
    }
3517
3518
    /**
3519
     * @return string
3520
     */
3521
    public function getNameNoTags()
3522
    {
3523
        return strip_tags($this->get_name());
3524
    }
3525
3526
    /**
3527
     * Gets a link to the resource from the present location, depending on item ID.
3528
     *
3529
     * @param string $type         Type of link expected
3530
     * @param int    $item_id      Learnpath item ID
3531
     * @param bool   $provided_toc
3532
     *
3533
     * @return string $provided_toc Link to the lp_item resource
3534
     */
3535
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3536
    {
3537
        $course_id = $this->get_course_int_id();
3538
        $item_id = (int) $item_id;
3539
3540
        if (empty($item_id)) {
3541
            $item_id = $this->get_current_item_id();
3542
3543
            if (empty($item_id)) {
3544
                //still empty, this means there was no item_id given and we are not in an object context or
3545
                //the object property is empty, return empty link
3546
                $this->first();
3547
3548
                return '';
3549
            }
3550
        }
3551
3552
        $file = '';
3553
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3554
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3555
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3556
3557
        $sql = "SELECT
3558
                    l.lp_type as ltype,
3559
                    l.path as lpath,
3560
                    li.item_type as litype,
3561
                    li.path as lipath,
3562
                    li.parameters as liparams
3563
        		FROM $lp_table l
3564
                INNER JOIN $lp_item_table li
3565
                ON (li.lp_id = l.iid)
3566
        		WHERE
3567
        		    li.iid = $item_id
3568
        		";
3569
        $res = Database::query($sql);
3570
        if (Database::num_rows($res) > 0) {
3571
            $row = Database::fetch_array($res);
3572
            $lp_type = $row['ltype'];
3573
            $lp_path = $row['lpath'];
3574
            $lp_item_type = $row['litype'];
3575
            $lp_item_path = $row['lipath'];
3576
            $lp_item_params = $row['liparams'];
3577
3578
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3579
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3580
            }
3581
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3582
            if ($type === 'http') {
3583
                //web path
3584
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3585
            } else {
3586
                $course_path = $sys_course_path; //system path
3587
            }
3588
3589
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3590
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3591
            if (in_array(
3592
                $lp_item_type,
3593
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3594
            )
3595
            ) {
3596
                $lp_type = 1;
3597
            }
3598
3599
            // Now go through the specific cases to get the end of the path
3600
            // @todo Use constants instead of int values.
3601
            switch ($lp_type) {
3602
                case 1:
3603
                    $file = self::rl_get_resource_link_for_learnpath(
3604
                        $course_id,
3605
                        $this->get_id(),
3606
                        $item_id,
3607
                        $this->get_view_id(),
3608
                        $this->get_lp_session_id()
3609
                    );
3610
                    switch ($lp_item_type) {
3611
                        case 'document':
3612
                            // Shows a button to download the file instead of just downloading the file directly.
3613
                            $documentPathInfo = pathinfo($file);
3614
                            if (isset($documentPathInfo['extension'])) {
3615
                                $parsed = parse_url($documentPathInfo['extension']);
3616
                                if (isset($parsed['path'])) {
3617
                                    $extension = $parsed['path'];
3618
                                    $extensionsToDownload = [
3619
                                        'zip',
3620
                                        'ppt',
3621
                                        'pptx',
3622
                                        'ods',
3623
                                        'xlsx',
3624
                                        'xls',
3625
                                        'csv',
3626
                                        'doc',
3627
                                        'docx',
3628
                                        'dot',
3629
                                    ];
3630
3631
                                    if (in_array($extension, $extensionsToDownload)) {
3632
                                        $file = api_get_path(WEB_CODE_PATH).
3633
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3634
                                    }
3635
                                }
3636
                            }
3637
                            break;
3638
                        case 'dir':
3639
                            $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3640
                            break;
3641
                        case 'link':
3642
                            if (Link::is_youtube_link($file)) {
3643
                                $src = Link::get_youtube_video_id($file);
3644
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3645
                            } elseif (Link::isVimeoLink($file)) {
3646
                                $src = Link::getVimeoLinkId($file);
3647
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3648
                            } else {
3649
                                // If the current site is HTTPS and the link is
3650
                                // HTTP, browsers will refuse opening the link
3651
                                $urlId = api_get_current_access_url_id();
3652
                                $url = api_get_access_url($urlId, false);
3653
                                $protocol = substr($url['url'], 0, 5);
3654
                                if ($protocol === 'https') {
3655
                                    $linkProtocol = substr($file, 0, 5);
3656
                                    if ($linkProtocol === 'http:') {
3657
                                        //this is the special intervention case
3658
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3659
                                    }
3660
                                }
3661
                            }
3662
                            break;
3663
                        case 'quiz':
3664
                            // Check how much attempts of a exercise exits in lp
3665
                            $lp_item_id = $this->get_current_item_id();
3666
                            $lp_view_id = $this->get_view_id();
3667
3668
                            $prevent_reinit = null;
3669
                            if (isset($this->items[$this->current])) {
3670
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3671
                            }
3672
3673
                            if (empty($provided_toc)) {
3674
                                if ($this->debug > 0) {
3675
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3676
                                }
3677
                                $list = $this->get_toc();
3678
                            } else {
3679
                                if ($this->debug > 0) {
3680
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3681
                                }
3682
                                $list = $provided_toc;
3683
                            }
3684
3685
                            $type_quiz = false;
3686
                            foreach ($list as $toc) {
3687
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3688
                                    $type_quiz = true;
3689
                                }
3690
                            }
3691
3692
                            if ($type_quiz) {
3693
                                $lp_item_id = (int) $lp_item_id;
3694
                                $lp_view_id = (int) $lp_view_id;
3695
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3696
                                        WHERE
3697
                                            c_id = $course_id AND
3698
                                            lp_item_id='".$lp_item_id."' AND
3699
                                            lp_view_id ='".$lp_view_id."' AND
3700
                                            status='completed'";
3701
                                $result = Database::query($sql);
3702
                                $row_count = Database:: fetch_row($result);
3703
                                $count_item_view = (int) $row_count[0];
3704
                                $not_multiple_attempt = 0;
3705
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3706
                                    $not_multiple_attempt = 1;
3707
                                }
3708
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3709
                            }
3710
                            break;
3711
                    }
3712
3713
                    $tmp_array = explode('/', $file);
3714
                    $document_name = $tmp_array[count($tmp_array) - 1];
3715
                    if (strpos($document_name, '_DELETED_')) {
3716
                        $file = 'blank.php?error=document_deleted';
3717
                    }
3718
                    break;
3719
                case 2:
3720
                    if ($this->debug > 2) {
3721
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3722
                    }
3723
3724
                    if ($lp_item_type != 'dir') {
3725
                        // Quite complex here:
3726
                        // We want to make sure 'http://' (and similar) links can
3727
                        // be loaded as is (withouth the Chamilo path in front) but
3728
                        // some contents use this form: resource.htm?resource=http://blablabla
3729
                        // which means we have to find a protocol at the path's start, otherwise
3730
                        // it should not be considered as an external URL.
3731
                        // if ($this->prerequisites_match($item_id)) {
3732
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3733
                            if ($this->debug > 2) {
3734
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3735
                            }
3736
                            // Distant url, return as is.
3737
                            $file = $lp_item_path;
3738
                        } else {
3739
                            if ($this->debug > 2) {
3740
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3741
                            }
3742
                            // Prevent getting untranslatable urls.
3743
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3744
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3745
                            // Prepare the path.
3746
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3747
                            // TODO: Fix this for urls with protocol header.
3748
                            $file = str_replace('//', '/', $file);
3749
                            $file = str_replace(':/', '://', $file);
3750
                            if (substr($lp_path, -1) == '/') {
3751
                                $lp_path = substr($lp_path, 0, -1);
3752
                            }
3753
3754
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3755
                                // if file not found.
3756
                                $decoded = html_entity_decode($lp_item_path);
3757
                                list($decoded) = explode('?', $decoded);
3758
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3759
                                    $file = self::rl_get_resource_link_for_learnpath(
3760
                                        $course_id,
3761
                                        $this->get_id(),
3762
                                        $item_id,
3763
                                        $this->get_view_id()
3764
                                    );
3765
                                    if (empty($file)) {
3766
                                        $file = 'blank.php?error=document_not_found';
3767
                                    } else {
3768
                                        $tmp_array = explode('/', $file);
3769
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3770
                                        if (strpos($document_name, '_DELETED_')) {
3771
                                            $file = 'blank.php?error=document_deleted';
3772
                                        } else {
3773
                                            $file = 'blank.php?error=document_not_found';
3774
                                        }
3775
                                    }
3776
                                } else {
3777
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3778
                                }
3779
                            }
3780
                        }
3781
3782
                        // We want to use parameters if they were defined in the imsmanifest
3783
                        if (strpos($file, 'blank.php') === false) {
3784
                            $lp_item_params = ltrim($lp_item_params, '?');
3785
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3786
                        }
3787
                    } else {
3788
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3789
                    }
3790
                    break;
3791
                case 3:
3792
                    if ($this->debug > 2) {
3793
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3794
                    }
3795
                    // Formatting AICC HACP append URL.
3796
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3797
                    if (!empty($lp_item_params)) {
3798
                        $aicc_append .= $lp_item_params.'&';
3799
                    }
3800
                    if ($lp_item_type != 'dir') {
3801
                        // Quite complex here:
3802
                        // We want to make sure 'http://' (and similar) links can
3803
                        // be loaded as is (withouth the Chamilo path in front) but
3804
                        // some contents use this form: resource.htm?resource=http://blablabla
3805
                        // which means we have to find a protocol at the path's start, otherwise
3806
                        // it should not be considered as an external URL.
3807
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3808
                            if ($this->debug > 2) {
3809
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3810
                            }
3811
                            // Distant url, return as is.
3812
                            $file = $lp_item_path;
3813
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3814
                            /*
3815
                            if (stristr($file,'<servername>') !== false) {
3816
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3817
                            }
3818
                            */
3819
                            if (stripos($file, '<servername>') !== false) {
3820
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3821
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3822
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3823
                            }
3824
3825
                            $file .= $aicc_append;
3826
                        } else {
3827
                            if ($this->debug > 2) {
3828
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3829
                            }
3830
                            // Prevent getting untranslatable urls.
3831
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3832
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3833
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3834
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3835
                            // TODO: Fix this for urls with protocol header.
3836
                            $file = str_replace('//', '/', $file);
3837
                            $file = str_replace(':/', '://', $file);
3838
                            $file .= $aicc_append;
3839
                        }
3840
                    } else {
3841
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3842
                    }
3843
                    break;
3844
                case 4:
3845
                    break;
3846
                default:
3847
                    break;
3848
            }
3849
            // Replace &amp; by & because &amp; will break URL with params
3850
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3851
        }
3852
        if ($this->debug > 2) {
3853
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3854
        }
3855
3856
        return $file;
3857
    }
3858
3859
    /**
3860
     * Gets the latest usable view or generate a new one.
3861
     *
3862
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3863
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3864
     *
3865
     * @return int DB lp_view id
3866
     */
3867
    public function get_view($attempt_num = 0, $userId = null)
3868
    {
3869
        $search = '';
3870
        // Use $attempt_num to enable multi-views management (disabled so far).
3871
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3872
            $search = 'AND view_count = '.$attempt_num;
3873
        }
3874
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3875
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3876
3877
        $course_id = api_get_course_int_id();
3878
        $sessionId = api_get_session_id();
3879
3880
        // Check user ID.
3881
        if (empty($userId)) {
3882
            if (empty($this->get_user_id())) {
3883
                $this->error = 'User ID is empty in learnpath::get_view()';
3884
3885
                return null;
3886
            } else {
3887
                $userId = $this->get_user_id();
3888
            }
3889
        }
3890
3891
        $sql = "SELECT iid, view_count FROM $lp_view_table
3892
        		WHERE
3893
        		    c_id = $course_id AND
3894
        		    lp_id = ".$this->get_id()." AND
3895
        		    user_id = ".$userId." AND
3896
        		    session_id = $sessionId
3897
        		    $search
3898
                ORDER BY view_count DESC";
3899
        $res = Database::query($sql);
3900
        if (Database::num_rows($res) > 0) {
3901
            $row = Database::fetch_array($res);
3902
            $this->lp_view_id = $row['iid'];
3903
        } elseif (!api_is_invitee()) {
3904
            // There is no database record, create one.
3905
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3906
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3907
            Database::query($sql);
3908
            $id = Database::insert_id();
3909
            $this->lp_view_id = $id;
3910
3911
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3912
            Database::query($sql);
3913
        }
3914
3915
        return $this->lp_view_id;
3916
    }
3917
3918
    /**
3919
     * Gets the current view id.
3920
     *
3921
     * @return int View ID (from lp_view)
3922
     */
3923
    public function get_view_id()
3924
    {
3925
        if (!empty($this->lp_view_id)) {
3926
            return (int) $this->lp_view_id;
3927
        }
3928
3929
        return 0;
3930
    }
3931
3932
    /**
3933
     * Gets the update queue.
3934
     *
3935
     * @return array Array containing IDs of items to be updated by JavaScript
3936
     */
3937
    public function get_update_queue()
3938
    {
3939
        return $this->update_queue;
3940
    }
3941
3942
    /**
3943
     * Gets the user ID.
3944
     *
3945
     * @return int User ID
3946
     */
3947
    public function get_user_id()
3948
    {
3949
        if (!empty($this->user_id)) {
3950
            return (int) $this->user_id;
3951
        }
3952
3953
        return false;
3954
    }
3955
3956
    /**
3957
     * Checks if any of the items has an audio element attached.
3958
     *
3959
     * @return bool True or false
3960
     */
3961
    public function has_audio()
3962
    {
3963
        $has = false;
3964
        foreach ($this->items as $i => $item) {
3965
            if (!empty($this->items[$i]->audio)) {
3966
                $has = true;
3967
                break;
3968
            }
3969
        }
3970
3971
        return $has;
3972
    }
3973
3974
    /**
3975
     * Moves an item up and down at its level.
3976
     *
3977
     * @param int    $id        Item to move up and down
3978
     * @param string $direction Direction 'up' or 'down'
3979
     *
3980
     * @return bool|int
3981
     */
3982
    public function move_item($id, $direction)
3983
    {
3984
        $course_id = api_get_course_int_id();
3985
        if (empty($id) || empty($direction)) {
3986
            return false;
3987
        }
3988
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3989
        $sql_sel = "SELECT *
3990
                    FROM $tbl_lp_item
3991
                    WHERE
3992
                        iid = $id
3993
                    ";
3994
        $res_sel = Database::query($sql_sel);
3995
        // Check if elem exists.
3996
        if (Database::num_rows($res_sel) < 1) {
3997
            return false;
3998
        }
3999
        // Gather data.
4000
        $row = Database::fetch_array($res_sel);
4001
        $previous = $row['previous_item_id'];
4002
        $next = $row['next_item_id'];
4003
        $display = $row['display_order'];
4004
        $parent = $row['parent_item_id'];
4005
        $lp = $row['lp_id'];
4006
        // Update the item (switch with previous/next one).
4007
        switch ($direction) {
4008
            case 'up':
4009
                if ($display > 1) {
4010
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4011
                                 WHERE iid = $previous";
4012
                    $res_sel2 = Database::query($sql_sel2);
4013
                    if (Database::num_rows($res_sel2) < 1) {
4014
                        $previous_previous = 0;
4015
                    }
4016
                    // Gather data.
4017
                    $row2 = Database::fetch_array($res_sel2);
4018
                    $previous_previous = $row2['previous_item_id'];
4019
                    // Update previous_previous item (switch "next" with current).
4020
                    if ($previous_previous != 0) {
4021
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4022
                                        next_item_id = $id
4023
                                    WHERE iid = $previous_previous";
4024
                        Database::query($sql_upd2);
4025
                    }
4026
                    // Update previous item (switch with current).
4027
                    if ($previous != 0) {
4028
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4029
                                    next_item_id = $next,
4030
                                    previous_item_id = $id,
4031
                                    display_order = display_order +1
4032
                                    WHERE iid = $previous";
4033
                        Database::query($sql_upd2);
4034
                    }
4035
4036
                    // Update current item (switch with previous).
4037
                    if ($id != 0) {
4038
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4039
                                        next_item_id = $previous,
4040
                                        previous_item_id = $previous_previous,
4041
                                        display_order = display_order-1
4042
                                    WHERE c_id = ".$course_id." AND id = $id";
4043
                        Database::query($sql_upd2);
4044
                    }
4045
                    // Update next item (new previous item).
4046
                    if (!empty($next)) {
4047
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4048
                                     WHERE iid = $next";
4049
                        Database::query($sql_upd2);
4050
                    }
4051
                    $display = $display - 1;
4052
                }
4053
                break;
4054
            case 'down':
4055
                if ($next != 0) {
4056
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4057
                                 WHERE iid = $next";
4058
                    $res_sel2 = Database::query($sql_sel2);
4059
                    if (Database::num_rows($res_sel2) < 1) {
4060
                        $next_next = 0;
4061
                    }
4062
                    // Gather data.
4063
                    $row2 = Database::fetch_array($res_sel2);
4064
                    $next_next = $row2['next_item_id'];
4065
                    // Update previous item (switch with current).
4066
                    if ($previous != 0) {
4067
                        $sql_upd2 = "UPDATE $tbl_lp_item
4068
                                     SET next_item_id = $next
4069
                                     WHERE iid = $previous";
4070
                        Database::query($sql_upd2);
4071
                    }
4072
                    // Update current item (switch with previous).
4073
                    if ($id != 0) {
4074
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4075
                                     previous_item_id = $next,
4076
                                     next_item_id = $next_next,
4077
                                     display_order = display_order + 1
4078
                                     WHERE iid = $id";
4079
                        Database::query($sql_upd2);
4080
                    }
4081
4082
                    // Update next item (new previous item).
4083
                    if ($next != 0) {
4084
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4085
                                     previous_item_id = $previous,
4086
                                     next_item_id = $id,
4087
                                     display_order = display_order-1
4088
                                     WHERE iid = $next";
4089
                        Database::query($sql_upd2);
4090
                    }
4091
4092
                    // Update next_next item (switch "previous" with current).
4093
                    if ($next_next != 0) {
4094
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4095
                                     previous_item_id = $id
4096
                                     WHERE iid = $next_next";
4097
                        Database::query($sql_upd2);
4098
                    }
4099
                    $display = $display + 1;
4100
                }
4101
                break;
4102
            default:
4103
                return false;
4104
        }
4105
4106
        return $display;
4107
    }
4108
4109
    /**
4110
     * Move a LP up (display_order).
4111
     *
4112
     * @param int $lp_id      Learnpath ID
4113
     * @param int $categoryId Category ID
4114
     *
4115
     * @return bool
4116
     */
4117
    public static function move_up($lp_id, $categoryId = 0)
4118
    {
4119
        $courseId = api_get_course_int_id();
4120
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4121
4122
        $categoryCondition = '';
4123
        if (!empty($categoryId)) {
4124
            $categoryId = (int) $categoryId;
4125
            $categoryCondition = " AND category_id = $categoryId";
4126
        }
4127
        $sql = "SELECT * FROM $lp_table
4128
                WHERE c_id = $courseId
4129
                $categoryCondition
4130
                ORDER BY display_order";
4131
        $res = Database::query($sql);
4132
        if ($res === false) {
4133
            return false;
4134
        }
4135
4136
        $lps = [];
4137
        $lp_order = [];
4138
        $num = Database::num_rows($res);
4139
        // First check the order is correct, globally (might be wrong because
4140
        // of versions < 1.8.4)
4141
        if ($num > 0) {
4142
            $i = 1;
4143
            while ($row = Database::fetch_array($res)) {
4144
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4145
                    $sql = "UPDATE $lp_table SET display_order = $i
4146
                            WHERE iid = ".$row['iid'];
4147
                    Database::query($sql);
4148
                }
4149
                $row['display_order'] = $i;
4150
                $lps[$row['iid']] = $row;
4151
                $lp_order[$i] = $row['iid'];
4152
                $i++;
4153
            }
4154
        }
4155
        if ($num > 1) { // If there's only one element, no need to sort.
4156
            $order = $lps[$lp_id]['display_order'];
4157
            if ($order > 1) { // If it's the first element, no need to move up.
4158
                $sql = "UPDATE $lp_table SET display_order = $order
4159
                        WHERE iid = ".$lp_order[$order - 1];
4160
                Database::query($sql);
4161
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4162
                        WHERE iid = $lp_id";
4163
                Database::query($sql);
4164
            }
4165
        }
4166
4167
        return true;
4168
    }
4169
4170
    /**
4171
     * Move a learnpath down (display_order).
4172
     *
4173
     * @param int $lp_id      Learnpath ID
4174
     * @param int $categoryId Category ID
4175
     *
4176
     * @return bool
4177
     */
4178
    public static function move_down($lp_id, $categoryId = 0)
4179
    {
4180
        $courseId = api_get_course_int_id();
4181
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4182
4183
        $categoryCondition = '';
4184
        if (!empty($categoryId)) {
4185
            $categoryId = (int) $categoryId;
4186
            $categoryCondition = " AND category_id = $categoryId";
4187
        }
4188
4189
        $sql = "SELECT * FROM $lp_table
4190
                WHERE c_id = $courseId
4191
                $categoryCondition
4192
                ORDER BY display_order";
4193
        $res = Database::query($sql);
4194
        if ($res === false) {
4195
            return false;
4196
        }
4197
        $lps = [];
4198
        $lp_order = [];
4199
        $num = Database::num_rows($res);
4200
        $max = 0;
4201
        // First check the order is correct, globally (might be wrong because
4202
        // of versions < 1.8.4).
4203
        if ($num > 0) {
4204
            $i = 1;
4205
            while ($row = Database::fetch_array($res)) {
4206
                $max = $i;
4207
                if ($row['display_order'] != $i) {
4208
                    // If we find a gap in the order, we need to fix it.
4209
                    $sql = "UPDATE $lp_table SET display_order = $i
4210
                              WHERE iid = ".$row['iid'];
4211
                    Database::query($sql);
4212
                }
4213
                $row['display_order'] = $i;
4214
                $lps[$row['iid']] = $row;
4215
                $lp_order[$i] = $row['iid'];
4216
                $i++;
4217
            }
4218
        }
4219
        if ($num > 1) { // If there's only one element, no need to sort.
4220
            $order = $lps[$lp_id]['display_order'];
4221
            if ($order < $max) { // If it's the first element, no need to move up.
4222
                $sql = "UPDATE $lp_table SET display_order = $order
4223
                        WHERE iid = ".$lp_order[$order + 1];
4224
                Database::query($sql);
4225
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4226
                        WHERE iid = $lp_id";
4227
                Database::query($sql);
4228
            }
4229
        }
4230
4231
        return true;
4232
    }
4233
4234
    /**
4235
     * Updates learnpath attributes to point to the next element
4236
     * The last part is similar to set_current_item but processing the other way around.
4237
     */
4238
    public function next()
4239
    {
4240
        if ($this->debug > 0) {
4241
            error_log('In learnpath::next()', 0);
4242
        }
4243
        $this->last = $this->get_current_item_id();
4244
        $this->items[$this->last]->save(
4245
            false,
4246
            $this->prerequisites_match($this->last)
4247
        );
4248
        $this->autocomplete_parents($this->last);
4249
        $new_index = $this->get_next_index();
4250
        if ($this->debug > 2) {
4251
            error_log('New index: '.$new_index, 0);
4252
        }
4253
        $this->index = $new_index;
4254
        if ($this->debug > 2) {
4255
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4256
        }
4257
        $this->current = $this->ordered_items[$new_index];
4258
        if ($this->debug > 2) {
4259
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4260
        }
4261
    }
4262
4263
    /**
4264
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4265
     * class, this might be redefined to allow several behaviours depending on the document type.
4266
     *
4267
     * @param int $id Resource ID
4268
     */
4269
    public function open($id)
4270
    {
4271
        // TODO:
4272
        // set the current resource attribute to this resource
4273
        // switch on element type (redefine in child class?)
4274
        // set status for this item to "opened"
4275
        // start timer
4276
        // initialise score
4277
        $this->index = 0; //or = the last item seen (see $this->last)
4278
    }
4279
4280
    /**
4281
     * Check that all prerequisites are fulfilled. Returns true and an
4282
     * empty string on success, returns false
4283
     * and the prerequisite string on error.
4284
     * This function is based on the rules for aicc_script language as
4285
     * described in the SCORM 1.2 CAM documentation page 108.
4286
     *
4287
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4288
     *
4289
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4290
     *              string otherwise
4291
     */
4292
    public function prerequisites_match($itemId = null)
4293
    {
4294
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4295
        if ($allow) {
4296
            if (api_is_allowed_to_edit() ||
4297
                api_is_platform_admin(true) ||
4298
                api_is_drh() ||
4299
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4300
            ) {
4301
                return true;
4302
            }
4303
        }
4304
4305
        $debug = $this->debug;
4306
        if ($debug > 0) {
4307
            error_log('In learnpath::prerequisites_match()');
4308
        }
4309
4310
        if (empty($itemId)) {
4311
            $itemId = $this->current;
4312
        }
4313
4314
        $currentItem = $this->getItem($itemId);
4315
        if ($debug > 0) {
4316
            error_log("Checking item id $itemId");
4317
        }
4318
4319
        if ($currentItem) {
4320
            if ($this->type == 2) {
4321
                // Getting prereq from scorm
4322
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4323
            } else {
4324
                $prereq_string = $currentItem->get_prereq_string();
4325
            }
4326
4327
            if (empty($prereq_string)) {
4328
                if ($debug > 0) {
4329
                    error_log('Found prereq_string is empty return true');
4330
                }
4331
4332
                return true;
4333
            }
4334
4335
            // Clean spaces.
4336
            $prereq_string = str_replace(' ', '', $prereq_string);
4337
            if ($debug > 0) {
4338
                error_log('Found prereq_string: '.$prereq_string, 0);
4339
            }
4340
4341
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4342
            $result = $currentItem->parse_prereq(
4343
                $prereq_string,
4344
                $this->items,
4345
                $this->refs_list,
4346
                $this->get_user_id()
4347
            );
4348
4349
            if ($result === false) {
4350
                $this->set_error_msg($currentItem->prereq_alert);
4351
            }
4352
        } else {
4353
            $result = true;
4354
            if ($debug > 1) {
4355
                error_log('$this->items['.$itemId.'] was not an object');
4356
            }
4357
        }
4358
4359
        if ($debug > 1) {
4360
            error_log('Result: '.$result);
4361
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4362
        }
4363
4364
        return $result;
4365
    }
4366
4367
    /**
4368
     * Updates learnpath attributes to point to the previous element
4369
     * The last part is similar to set_current_item but processing the other way around.
4370
     */
4371
    public function previous()
4372
    {
4373
        $this->last = $this->get_current_item_id();
4374
        $this->items[$this->last]->save(
4375
            false,
4376
            $this->prerequisites_match($this->last)
4377
        );
4378
        $this->autocomplete_parents($this->last);
4379
        $new_index = $this->get_previous_index();
4380
        $this->index = $new_index;
4381
        $this->current = $this->ordered_items[$new_index];
4382
    }
4383
4384
    /**
4385
     * Publishes a learnpath. This basically means show or hide the learnpath
4386
     * to normal users.
4387
     * Can be used as abstract.
4388
     *
4389
     * @param int $lp_id          Learnpath ID
4390
     * @param int $set_visibility New visibility
4391
     *
4392
     * @return bool
4393
     */
4394
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4395
    {
4396
        $action = 'visible';
4397
        if ($set_visibility != 1) {
4398
            $action = 'invisible';
4399
            self::toggle_publish($lp_id, 'i');
4400
        }
4401
4402
        return api_item_property_update(
4403
            api_get_course_info(),
4404
            TOOL_LEARNPATH,
4405
            $lp_id,
4406
            $action,
4407
            api_get_user_id()
4408
        );
4409
    }
4410
4411
    /**
4412
     * Publishes a learnpath category.
4413
     * This basically means show or hide the learnpath category to normal users.
4414
     *
4415
     * @param int $id
4416
     * @param int $visibility
4417
     *
4418
     * @return bool
4419
     */
4420
    public static function toggleCategoryVisibility($id, $visibility = 1)
4421
    {
4422
        $action = 'visible';
4423
        if ($visibility != 1) {
4424
            self::toggleCategoryPublish($id, 0);
4425
            $action = 'invisible';
4426
        }
4427
4428
        return api_item_property_update(
4429
            api_get_course_info(),
4430
            TOOL_LEARNPATH_CATEGORY,
4431
            $id,
4432
            $action,
4433
            api_get_user_id()
4434
        );
4435
    }
4436
4437
    /**
4438
     * Publishes a learnpath. This basically means show or hide the learnpath
4439
     * on the course homepage
4440
     * Can be used as abstract.
4441
     *
4442
     * @param int    $lp_id          Learnpath id
4443
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4444
     *
4445
     * @return bool
4446
     */
4447
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4448
    {
4449
        $course_id = api_get_course_int_id();
4450
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4451
        $lp_id = (int) $lp_id;
4452
        $sql = "SELECT * FROM $tbl_lp
4453
                WHERE iid = $lp_id";
4454
        $result = Database::query($sql);
4455
        if (Database::num_rows($result)) {
4456
            $row = Database::fetch_array($result);
4457
            $name = Database::escape_string($row['name']);
4458
            if ($set_visibility == 'i') {
4459
                $v = 0;
4460
            }
4461
            if ($set_visibility == 'v') {
4462
                $v = 1;
4463
            }
4464
4465
            $session_id = api_get_session_id();
4466
            $session_condition = api_get_session_condition($session_id);
4467
4468
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4469
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4470
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4471
4472
            $sql = "SELECT * FROM $tbl_tool
4473
                    WHERE
4474
                        c_id = $course_id AND
4475
                        (link = '$link' OR link = '$oldLink') AND
4476
                        image = 'scormbuilder.gif' AND
4477
                        (
4478
                            link LIKE '$link%' OR
4479
                            link LIKE '$oldLink%'
4480
                        )
4481
                        $session_condition
4482
                    ";
4483
4484
            $result = Database::query($sql);
4485
            $num = Database::num_rows($result);
4486
            if ($set_visibility == 'i' && $num > 0) {
4487
                $sql = "DELETE FROM $tbl_tool
4488
                        WHERE
4489
                            c_id = $course_id AND
4490
                            (link = '$link' OR link = '$oldLink') AND
4491
                            image='scormbuilder.gif'
4492
                            $session_condition";
4493
                Database::query($sql);
4494
            } elseif ($set_visibility == 'v' && $num == 0) {
4495
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4496
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4497
                Database::query($sql);
4498
                $insertId = Database::insert_id();
4499
                if ($insertId) {
4500
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4501
                    Database::query($sql);
4502
                }
4503
            } elseif ($set_visibility == 'v' && $num > 0) {
4504
                $sql = "UPDATE $tbl_tool SET
4505
                            c_id = $course_id,
4506
                            name = '$name',
4507
                            link = '$link',
4508
                            image = 'scormbuilder.gif',
4509
                            visibility = '$v',
4510
                            admin = '0',
4511
                            address = 'pastillegris.gif',
4512
                            added_tool = 0,
4513
                            session_id = $session_id
4514
                        WHERE
4515
                            c_id = ".$course_id." AND
4516
                            (link = '$link' OR link = '$oldLink') AND
4517
                            image='scormbuilder.gif'
4518
                            $session_condition
4519
                        ";
4520
                Database::query($sql);
4521
            }
4522
        }
4523
4524
        return false;
4525
    }
4526
4527
    /**
4528
     * Publishes a learnpath.
4529
     * Show or hide the learnpath category on the course homepage.
4530
     *
4531
     * @param int $id
4532
     * @param int $setVisibility
4533
     *
4534
     * @throws \Doctrine\ORM\NonUniqueResultException
4535
     * @throws \Doctrine\ORM\ORMException
4536
     * @throws \Doctrine\ORM\OptimisticLockException
4537
     * @throws \Doctrine\ORM\TransactionRequiredException
4538
     *
4539
     * @return bool
4540
     */
4541
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4542
    {
4543
        $courseId = api_get_course_int_id();
4544
        $sessionId = api_get_session_id();
4545
        $sessionCondition = api_get_session_condition(
4546
            $sessionId,
4547
            true,
4548
            false,
4549
            't.sessionId'
4550
        );
4551
4552
        $em = Database::getManager();
4553
        $category = self::getCategory($id);
4554
4555
        if (!$category) {
4556
            return false;
4557
        }
4558
4559
        if (empty($courseId)) {
4560
            return false;
4561
        }
4562
4563
        $link = self::getCategoryLinkForTool($id);
4564
4565
        /** @var CTool $tool */
4566
        $tool = $em->createQuery("
4567
                SELECT t FROM ChamiloCourseBundle:CTool t
4568
                WHERE
4569
                    t.cId = :course AND
4570
                    t.link = :link1 AND
4571
                    t.image = 'lp_category.gif' AND
4572
                    t.link LIKE :link2
4573
                    $sessionCondition
4574
            ")
4575
            ->setParameters([
4576
                'course' => $courseId,
4577
                'link1' => $link,
4578
                'link2' => "$link%",
4579
            ])
4580
            ->getOneOrNullResult();
4581
4582
        if ($setVisibility == 0 && $tool) {
4583
            $em->remove($tool);
4584
            $em->flush();
4585
4586
            return true;
4587
        }
4588
4589
        if ($setVisibility == 1 && !$tool) {
4590
            $tool = new CTool();
4591
            $tool
4592
                ->setCategory('authoring')
4593
                ->setCId($courseId)
4594
                ->setName(strip_tags($category->getName()))
4595
                ->setLink($link)
4596
                ->setImage('lp_category.gif')
4597
                ->setVisibility(1)
4598
                ->setAdmin(0)
4599
                ->setAddress('pastillegris.gif')
4600
                ->setAddedTool(0)
4601
                ->setSessionId($sessionId)
4602
                ->setTarget('_self');
4603
4604
            $em->persist($tool);
4605
            $em->flush();
4606
4607
            $tool->setId($tool->getIid());
4608
4609
            $em->persist($tool);
4610
            $em->flush();
4611
4612
            return true;
4613
        }
4614
4615
        if ($setVisibility == 1 && $tool) {
4616
            $tool
4617
                ->setName(strip_tags($category->getName()))
4618
                ->setVisibility(1);
4619
4620
            $em->persist($tool);
4621
            $em->flush();
4622
4623
            return true;
4624
        }
4625
4626
        return false;
4627
    }
4628
4629
    /**
4630
     * Check if the learnpath category is visible for a user.
4631
     *
4632
     * @param int
4633
     * @param int
4634
     *
4635
     * @return bool
4636
     */
4637
    public static function categoryIsVisibleForStudent(
4638
        CLpCategory $category,
4639
        User $user,
4640
        $courseId = 0,
4641
        $sessionId = 0
4642
    ) {
4643
        if (empty($category)) {
4644
            return false;
4645
        }
4646
4647
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4648
4649
        if ($isAllowedToEdit) {
4650
            return true;
4651
        }
4652
4653
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4654
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4655
4656
        $courseInfo = api_get_course_info_by_id($courseId);
4657
4658
        $categoryVisibility = api_get_item_visibility(
4659
            $courseInfo,
4660
            TOOL_LEARNPATH_CATEGORY,
4661
            $category->getId(),
4662
            $sessionId
4663
        );
4664
4665
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4666
            return false;
4667
        }
4668
4669
        $subscriptionSettings = self::getSubscriptionSettings();
4670
4671
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4672
            return true;
4673
        }
4674
4675
        $noUserSubscribed = false;
4676
        $noGroupSubscribed = true;
4677
        $users = $category->getUsers();
4678
        if (empty($users) || !$users->count()) {
4679
            $noUserSubscribed = true;
4680
        } elseif ($category->hasUserAdded($user)) {
4681
            return true;
4682
        }
4683
4684
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4685
        $em = Database::getManager();
4686
4687
        /** @var ItemPropertyRepository $itemRepo */
4688
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4689
4690
        /** @var CourseRepository $courseRepo */
4691
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4692
        $session = null;
4693
        if (!empty($sessionId)) {
4694
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4695
        }
4696
4697
        $course = $courseRepo->find($courseId);
4698
4699
        if ($courseId != 0) {
4700
            // Subscribed groups to a LP
4701
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4702
                TOOL_LEARNPATH_CATEGORY,
4703
                $category->getId(),
4704
                $course,
4705
                $session
4706
            );
4707
        }
4708
4709
        if (!empty($subscribedGroupsInLp)) {
4710
            $noGroupSubscribed = false;
4711
            if (!empty($groups)) {
4712
                $groups = array_column($groups, 'iid');
4713
                /** @var CItemProperty $item */
4714
                foreach ($subscribedGroupsInLp as $item) {
4715
                    if ($item->getGroup() &&
4716
                        in_array($item->getGroup()->getId(), $groups)
4717
                    ) {
4718
                        return true;
4719
                    }
4720
                }
4721
            }
4722
        }
4723
        $response = $noGroupSubscribed && $noUserSubscribed;
4724
4725
        return $response;
4726
    }
4727
4728
    /**
4729
     * Check if a learnpath category is published as course tool.
4730
     *
4731
     * @param int $courseId
4732
     *
4733
     * @return bool
4734
     */
4735
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4736
    {
4737
        $link = self::getCategoryLinkForTool($category->getId());
4738
        $em = Database::getManager();
4739
4740
        $tools = $em
4741
            ->createQuery("
4742
                SELECT t FROM ChamiloCourseBundle:CTool t
4743
                WHERE t.cId = :course AND
4744
                    t.name = :name AND
4745
                    t.image = 'lp_category.gif' AND
4746
                    t.link LIKE :link
4747
            ")
4748
            ->setParameters([
4749
                'course' => $courseId,
4750
                'name' => strip_tags($category->getName()),
4751
                'link' => "$link%",
4752
            ])
4753
            ->getResult();
4754
4755
        /** @var CTool $tool */
4756
        $tool = current($tools);
4757
4758
        return $tool ? $tool->getVisibility() : false;
4759
    }
4760
4761
    /**
4762
     * Restart the whole learnpath. Return the URL of the first element.
4763
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4764
     * To use a similar method  statically, use the create_new_attempt() method.
4765
     *
4766
     * @return bool
4767
     */
4768
    public function restart()
4769
    {
4770
        if ($this->debug > 0) {
4771
            error_log('In learnpath::restart()', 0);
4772
        }
4773
        // TODO
4774
        // Call autosave method to save the current progress.
4775
        //$this->index = 0;
4776
        if (api_is_invitee()) {
4777
            return false;
4778
        }
4779
        $session_id = api_get_session_id();
4780
        $course_id = api_get_course_int_id();
4781
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4782
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4783
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4784
        if ($this->debug > 2) {
4785
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4786
        }
4787
        Database::query($sql);
4788
        $view_id = Database::insert_id();
4789
4790
        if ($view_id) {
4791
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4792
            Database::query($sql);
4793
            $this->lp_view_id = $view_id;
4794
            $this->attempt = $this->attempt + 1;
4795
        } else {
4796
            $this->error = 'Could not insert into item_view table...';
4797
4798
            return false;
4799
        }
4800
        $this->autocomplete_parents($this->current);
4801
        foreach ($this->items as $index => $dummy) {
4802
            $this->items[$index]->restart();
4803
            $this->items[$index]->set_lp_view($this->lp_view_id);
4804
        }
4805
        $this->first();
4806
4807
        return true;
4808
    }
4809
4810
    /**
4811
     * Saves the current item.
4812
     *
4813
     * @return bool
4814
     */
4815
    public function save_current()
4816
    {
4817
        $debug = $this->debug;
4818
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4819
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4820
        if ($debug) {
4821
            error_log('save_current() saving item '.$this->current, 0);
4822
            error_log(''.print_r($this->items, true), 0);
4823
        }
4824
        if (isset($this->items[$this->current]) &&
4825
            is_object($this->items[$this->current])
4826
        ) {
4827
            if ($debug) {
4828
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4829
            }
4830
4831
            $res = $this->items[$this->current]->save(
4832
                false,
4833
                $this->prerequisites_match($this->current)
4834
            );
4835
            $this->autocomplete_parents($this->current);
4836
            $status = $this->items[$this->current]->get_status();
4837
            $this->update_queue[$this->current] = $status;
4838
4839
            if ($debug) {
4840
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4841
            }
4842
4843
            return $res;
4844
        }
4845
4846
        return false;
4847
    }
4848
4849
    /**
4850
     * Saves the given item.
4851
     *
4852
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4853
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4854
     *
4855
     * @return bool
4856
     */
4857
    public function save_item($item_id = null, $from_outside = true)
4858
    {
4859
        $debug = $this->debug;
4860
        if ($debug) {
4861
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4862
        }
4863
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4864
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4865
        if (empty($item_id)) {
4866
            $item_id = (int) $_REQUEST['id'];
4867
        }
4868
4869
        if (empty($item_id)) {
4870
            $item_id = $this->get_current_item_id();
4871
        }
4872
        if (isset($this->items[$item_id]) &&
4873
            is_object($this->items[$item_id])
4874
        ) {
4875
            // Saving the item.
4876
            $res = $this->items[$item_id]->save(
4877
                $from_outside,
4878
                $this->prerequisites_match($item_id)
4879
            );
4880
4881
            if ($debug) {
4882
                error_log('update_queue before:');
4883
                error_log(print_r($this->update_queue, 1));
4884
            }
4885
            $this->autocomplete_parents($item_id);
4886
4887
            $status = $this->items[$item_id]->get_status();
4888
            $this->update_queue[$item_id] = $status;
4889
4890
            if ($debug) {
4891
                error_log('get_status(): '.$status);
4892
                error_log('update_queue after:');
4893
                error_log(print_r($this->update_queue, 1));
4894
            }
4895
4896
            return $res;
4897
        }
4898
4899
        return false;
4900
    }
4901
4902
    /**
4903
     * Saves the last item seen's ID only in case.
4904
     */
4905
    public function save_last($score = null)
4906
    {
4907
        $course_id = api_get_course_int_id();
4908
        $debug = $this->debug;
4909
        if ($debug) {
4910
            error_log('In learnpath::save_last()', 0);
4911
        }
4912
        $session_condition = api_get_session_condition(
4913
            api_get_session_id(),
4914
            true,
4915
            false
4916
        );
4917
        $table = Database::get_course_table(TABLE_LP_VIEW);
4918
4919
        $userId = $this->get_user_id();
4920
        if (empty($userId)) {
4921
            $userId = api_get_user_id();
4922
            if ($debug) {
4923
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4924
            }
4925
        }
4926
        if (isset($this->current) && !api_is_invitee()) {
4927
            if ($debug) {
4928
                error_log('Saving current item ('.$this->current.') for later review', 0);
4929
            }
4930
            $sql = "UPDATE $table SET
4931
                        last_item = ".$this->get_current_item_id()."
4932
                    WHERE
4933
                        c_id = $course_id AND
4934
                        lp_id = ".$this->get_id()." AND
4935
                        user_id = ".$userId." ".$session_condition;
4936
            if ($debug) {
4937
                error_log('Saving last item seen : '.$sql, 0);
4938
            }
4939
            Database::query($sql);
4940
        }
4941
4942
        if (!api_is_invitee()) {
4943
            // Save progress.
4944
            list($progress) = $this->get_progress_bar_text('%');
4945
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4946
            $scoreAsProgress = $this->getUseScoreAsProgress();
4947
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4948
                if ($debug) {
4949
                    error_log("Return false: Dont save score: $score");
4950
                    error_log("progress: $progress");
4951
                }
4952
4953
                return false;
4954
            }
4955
4956
            if ($scoreAsProgress && $scoreAsProgressSetting) {
4957
                $storedProgress = self::getProgress(
4958
                    $this->get_id(),
4959
                    $userId,
4960
                    $course_id,
4961
                    $this->get_lp_session_id()
4962
                );
4963
4964
                // Check if the stored progress is higher than the new value
4965
                if ($storedProgress >= $progress) {
4966
                    if ($debug) {
4967
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
4968
                    }
4969
4970
                    return false;
4971
                }
4972
            }
4973
4974
            if ($progress >= 0 && $progress <= 100) {
4975
                // Check database.
4976
                $progress = (int) $progress;
4977
                $sql = "UPDATE $table SET
4978
                            progress = $progress
4979
                        WHERE
4980
                            c_id = $course_id AND
4981
                            lp_id = ".$this->get_id()." AND
4982
                            user_id = ".$userId." ".$session_condition;
4983
                // Ignore errors as some tables might not have the progress field just yet.
4984
                Database::query($sql);
4985
                if ($debug) {
4986
                    error_log($sql);
4987
                }
4988
                $this->progress_db = $progress;
4989
4990
                if (100 == $progress) {
4991
                    HookLearningPathEnd::create()
4992
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
4993
                        ->hookLearningPathEnd();
4994
                }
4995
            }
4996
        }
4997
    }
4998
4999
    /**
5000
     * Sets the current item ID (checks if valid and authorized first).
5001
     *
5002
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5003
     */
5004
    public function set_current_item($item_id = null)
5005
    {
5006
        $debug = $this->debug;
5007
        if ($debug) {
5008
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5009
        }
5010
        if (empty($item_id)) {
5011
            if ($debug) {
5012
                error_log('No new current item given, ignore...', 0);
5013
            }
5014
            // Do nothing.
5015
        } else {
5016
            if ($debug) {
5017
                error_log('New current item given is '.$item_id.'...', 0);
5018
            }
5019
            if (is_numeric($item_id)) {
5020
                $item_id = (int) $item_id;
5021
                // TODO: Check in database here.
5022
                $this->last = $this->current;
5023
                $this->current = $item_id;
5024
                // TODO: Update $this->index as well.
5025
                foreach ($this->ordered_items as $index => $item) {
5026
                    if ($item == $this->current) {
5027
                        $this->index = $index;
5028
                        break;
5029
                    }
5030
                }
5031
                if ($debug) {
5032
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5033
                }
5034
            } else {
5035
                if ($debug) {
5036
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5037
                }
5038
            }
5039
        }
5040
    }
5041
5042
    /**
5043
     * Sets the encoding.
5044
     *
5045
     * @param string $enc New encoding
5046
     *
5047
     * @return bool
5048
     *
5049
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5050
     */
5051
    public function set_encoding($enc = 'UTF-8')
5052
    {
5053
        $enc = api_refine_encoding_id($enc);
5054
        if (empty($enc)) {
5055
            $enc = api_get_system_encoding();
5056
        }
5057
        if (api_is_encoding_supported($enc)) {
5058
            $lp = $this->get_id();
5059
            if ($lp != 0) {
5060
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5061
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5062
                        WHERE iid = ".$lp;
5063
                $res = Database::query($sql);
5064
5065
                return $res;
5066
            }
5067
        }
5068
5069
        return false;
5070
    }
5071
5072
    /**
5073
     * Sets the JS lib setting in the database directly.
5074
     * This is the JavaScript library file this lp needs to load on startup.
5075
     *
5076
     * @param string $lib Proximity setting
5077
     *
5078
     * @return bool True on update success. False otherwise.
5079
     */
5080
    public function set_jslib($lib = '')
5081
    {
5082
        $lp = $this->get_id();
5083
5084
        if ($lp != 0) {
5085
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5086
            $lib = Database::escape_string($lib);
5087
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5088
                    WHERE iid = $lp";
5089
            $res = Database::query($sql);
5090
5091
            return $res;
5092
        }
5093
5094
        return false;
5095
    }
5096
5097
    /**
5098
     * Sets the name of the LP maker (publisher) (and save).
5099
     *
5100
     * @param string $name Optional string giving the new content_maker of this learnpath
5101
     *
5102
     * @return bool True
5103
     */
5104
    public function set_maker($name = '')
5105
    {
5106
        if (empty($name)) {
5107
            return false;
5108
        }
5109
        $this->maker = $name;
5110
        $table = Database::get_course_table(TABLE_LP_MAIN);
5111
        $lp_id = $this->get_id();
5112
        $sql = "UPDATE $table SET
5113
                content_maker = '".Database::escape_string($this->maker)."'
5114
                WHERE iid = $lp_id";
5115
        Database::query($sql);
5116
5117
        return true;
5118
    }
5119
5120
    /**
5121
     * Sets the name of the current learnpath (and save).
5122
     *
5123
     * @param string $name Optional string giving the new name of this learnpath
5124
     *
5125
     * @return bool True/False
5126
     */
5127
    public function set_name($name = null)
5128
    {
5129
        if (empty($name)) {
5130
            return false;
5131
        }
5132
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5133
        $name = Database::escape_string($name);
5134
5135
        $this->name = $name;
5136
5137
        $lp_id = $this->get_id();
5138
        $course_id = $this->course_info['real_id'];
5139
        $sql = "UPDATE $lp_table SET
5140
                name = '$name'
5141
                WHERE iid = $lp_id";
5142
        $result = Database::query($sql);
5143
        // If the lp is visible on the homepage, change his name there.
5144
        if (Database::affected_rows($result)) {
5145
            $session_id = api_get_session_id();
5146
            $session_condition = api_get_session_condition($session_id);
5147
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5148
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5149
            $sql = "UPDATE $tbl_tool SET name = '$name'
5150
            	    WHERE
5151
            	        c_id = $course_id AND
5152
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5153
            Database::query($sql);
5154
5155
            return true;
5156
        }
5157
5158
        return false;
5159
    }
5160
5161
    /**
5162
     * Set index specified prefix terms for all items in this path.
5163
     *
5164
     * @param string $terms_string Comma-separated list of terms
5165
     * @param string $prefix       Xapian term prefix
5166
     *
5167
     * @return bool False on error, true otherwise
5168
     */
5169
    public function set_terms_by_prefix($terms_string, $prefix)
5170
    {
5171
        $course_id = api_get_course_int_id();
5172
        if (api_get_setting('search_enabled') !== 'true') {
5173
            return false;
5174
        }
5175
5176
        if (!extension_loaded('xapian')) {
5177
            return false;
5178
        }
5179
5180
        $terms_string = trim($terms_string);
5181
        $terms = explode(',', $terms_string);
5182
        array_walk($terms, 'trim_value');
5183
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5184
5185
        // Don't do anything if no change, verify only at DB, not the search engine.
5186
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5187
            return false;
5188
        }
5189
5190
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5191
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5192
5193
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5194
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5195
        $lp_id = (int) $_POST['lp_id'];
5196
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5197
        $result = Database::query($sql);
5198
        $di = new ChamiloIndexer();
5199
5200
        while ($lp_item = Database::fetch_array($result)) {
5201
            // Get search_did.
5202
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5203
            $sql = 'SELECT * FROM %s
5204
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5205
                    LIMIT 1';
5206
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5207
5208
            //echo $sql; echo '<br>';
5209
            $res = Database::query($sql);
5210
            if (Database::num_rows($res) > 0) {
5211
                $se_ref = Database::fetch_array($res);
5212
                // Compare terms.
5213
                $doc = $di->get_document($se_ref['search_did']);
5214
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5215
                $xterms = [];
5216
                foreach ($xapian_terms as $xapian_term) {
5217
                    $xterms[] = substr($xapian_term['name'], 1);
5218
                }
5219
5220
                $dterms = $terms;
5221
                $missing_terms = array_diff($dterms, $xterms);
5222
                $deprecated_terms = array_diff($xterms, $dterms);
5223
5224
                // Save it to search engine.
5225
                foreach ($missing_terms as $term) {
5226
                    $doc->add_term($prefix.$term, 1);
5227
                }
5228
                foreach ($deprecated_terms as $term) {
5229
                    $doc->remove_term($prefix.$term);
5230
                }
5231
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5232
                $di->getDb()->flush();
5233
            }
5234
        }
5235
5236
        return true;
5237
    }
5238
5239
    /**
5240
     * Sets the theme of the LP (local/remote) (and save).
5241
     *
5242
     * @param string $name Optional string giving the new theme of this learnpath
5243
     *
5244
     * @return bool Returns true if theme name is not empty
5245
     */
5246
    public function set_theme($name = '')
5247
    {
5248
        $this->theme = $name;
5249
        $table = Database::get_course_table(TABLE_LP_MAIN);
5250
        $lp_id = $this->get_id();
5251
        $sql = "UPDATE $table
5252
                SET theme = '".Database::escape_string($this->theme)."'
5253
                WHERE iid = $lp_id";
5254
        Database::query($sql);
5255
5256
        return true;
5257
    }
5258
5259
    /**
5260
     * Sets the image of an LP (and save).
5261
     *
5262
     * @param string $name Optional string giving the new image of this learnpath
5263
     *
5264
     * @return bool Returns true if theme name is not empty
5265
     */
5266
    public function set_preview_image($name = '')
5267
    {
5268
        $this->preview_image = $name;
5269
        $table = Database::get_course_table(TABLE_LP_MAIN);
5270
        $lp_id = $this->get_id();
5271
        $sql = "UPDATE $table SET
5272
                preview_image = '".Database::escape_string($this->preview_image)."'
5273
                WHERE iid = $lp_id";
5274
        Database::query($sql);
5275
5276
        return true;
5277
    }
5278
5279
    /**
5280
     * Sets the author of a LP (and save).
5281
     *
5282
     * @param string $name Optional string giving the new author of this learnpath
5283
     *
5284
     * @return bool Returns true if author's name is not empty
5285
     */
5286
    public function set_author($name = '')
5287
    {
5288
        $this->author = $name;
5289
        $table = Database::get_course_table(TABLE_LP_MAIN);
5290
        $lp_id = $this->get_id();
5291
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5292
                WHERE iid = $lp_id";
5293
        Database::query($sql);
5294
5295
        return true;
5296
    }
5297
5298
    /**
5299
     * Sets the hide_toc_frame parameter of a LP (and save).
5300
     *
5301
     * @param int $hide 1 if frame is hidden 0 then else
5302
     *
5303
     * @return bool Returns true if author's name is not empty
5304
     */
5305
    public function set_hide_toc_frame($hide)
5306
    {
5307
        if (intval($hide) == $hide) {
5308
            $this->hide_toc_frame = $hide;
5309
            $table = Database::get_course_table(TABLE_LP_MAIN);
5310
            $lp_id = $this->get_id();
5311
            $sql = "UPDATE $table SET
5312
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5313
                    WHERE iid = $lp_id";
5314
            Database::query($sql);
5315
5316
            return true;
5317
        }
5318
5319
        return false;
5320
    }
5321
5322
    /**
5323
     * Sets the prerequisite of a LP (and save).
5324
     *
5325
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5326
     *
5327
     * @return bool returns true if prerequisite is not empty
5328
     */
5329
    public function set_prerequisite($prerequisite)
5330
    {
5331
        $this->prerequisite = (int) $prerequisite;
5332
        $table = Database::get_course_table(TABLE_LP_MAIN);
5333
        $lp_id = $this->get_id();
5334
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5335
                WHERE iid = $lp_id";
5336
        Database::query($sql);
5337
5338
        return true;
5339
    }
5340
5341
    /**
5342
     * Sets the location/proximity of the LP (local/remote) (and save).
5343
     *
5344
     * @param string $name Optional string giving the new location of this learnpath
5345
     *
5346
     * @return bool True on success / False on error
5347
     */
5348
    public function set_proximity($name = '')
5349
    {
5350
        if (empty($name)) {
5351
            return false;
5352
        }
5353
5354
        $this->proximity = $name;
5355
        $table = Database::get_course_table(TABLE_LP_MAIN);
5356
        $lp_id = $this->get_id();
5357
        $sql = "UPDATE $table SET
5358
                    content_local = '".Database::escape_string($name)."'
5359
                WHERE iid = $lp_id";
5360
        Database::query($sql);
5361
5362
        return true;
5363
    }
5364
5365
    /**
5366
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5367
     *
5368
     * @param int $id DB ID of the item
5369
     */
5370
    public function set_previous_item($id)
5371
    {
5372
        if ($this->debug > 0) {
5373
            error_log('In learnpath::set_previous_item()', 0);
5374
        }
5375
        $this->last = $id;
5376
    }
5377
5378
    /**
5379
     * Sets use_max_score.
5380
     *
5381
     * @param int $use_max_score Optional string giving the new location of this learnpath
5382
     *
5383
     * @return bool True on success / False on error
5384
     */
5385
    public function set_use_max_score($use_max_score = 1)
5386
    {
5387
        $use_max_score = (int) $use_max_score;
5388
        $this->use_max_score = $use_max_score;
5389
        $table = Database::get_course_table(TABLE_LP_MAIN);
5390
        $lp_id = $this->get_id();
5391
        $sql = "UPDATE $table SET
5392
                    use_max_score = '".$this->use_max_score."'
5393
                WHERE iid = $lp_id";
5394
        Database::query($sql);
5395
5396
        return true;
5397
    }
5398
5399
    /**
5400
     * Sets and saves the expired_on date.
5401
     *
5402
     * @param string $expired_on Optional string giving the new author of this learnpath
5403
     *
5404
     * @throws \Doctrine\ORM\OptimisticLockException
5405
     *
5406
     * @return bool Returns true if author's name is not empty
5407
     */
5408
    public function set_expired_on($expired_on)
5409
    {
5410
        $em = Database::getManager();
5411
        /** @var CLp $lp */
5412
        $lp = $em
5413
            ->getRepository('ChamiloCourseBundle:CLp')
5414
            ->findOneBy(
5415
                [
5416
                    'iid' => $this->get_id(),
5417
                ]
5418
            );
5419
5420
        if (!$lp) {
5421
            return false;
5422
        }
5423
5424
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5425
5426
        $lp->setExpiredOn($this->expired_on);
5427
        $em->persist($lp);
5428
        $em->flush();
5429
5430
        return true;
5431
    }
5432
5433
    /**
5434
     * Sets and saves the publicated_on date.
5435
     *
5436
     * @param string $publicated_on Optional string giving the new author of this learnpath
5437
     *
5438
     * @throws \Doctrine\ORM\OptimisticLockException
5439
     *
5440
     * @return bool Returns true if author's name is not empty
5441
     */
5442
    public function set_publicated_on($publicated_on)
5443
    {
5444
        $em = Database::getManager();
5445
        /** @var CLp $lp */
5446
        $lp = $em
5447
            ->getRepository('ChamiloCourseBundle:CLp')
5448
            ->findOneBy(
5449
                [
5450
                    'iid' => $this->get_id(),
5451
                ]
5452
            );
5453
5454
        if (!$lp) {
5455
            return false;
5456
        }
5457
5458
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5459
        $lp->setPublicatedOn($this->publicated_on);
5460
        $em->persist($lp);
5461
        $em->flush();
5462
5463
        return true;
5464
    }
5465
5466
    /**
5467
     * Sets and saves the expired_on date.
5468
     *
5469
     * @return bool Returns true if author's name is not empty
5470
     */
5471
    public function set_modified_on()
5472
    {
5473
        $this->modified_on = api_get_utc_datetime();
5474
        $table = Database::get_course_table(TABLE_LP_MAIN);
5475
        $lp_id = $this->get_id();
5476
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5477
                WHERE iid = $lp_id";
5478
        Database::query($sql);
5479
5480
        return true;
5481
    }
5482
5483
    /**
5484
     * Sets the object's error message.
5485
     *
5486
     * @param string $error Error message. If empty, reinits the error string
5487
     */
5488
    public function set_error_msg($error = '')
5489
    {
5490
        if ($this->debug > 0) {
5491
            error_log('In learnpath::set_error_msg()', 0);
5492
        }
5493
        if (empty($error)) {
5494
            $this->error = '';
5495
        } else {
5496
            $this->error .= $error;
5497
        }
5498
    }
5499
5500
    /**
5501
     * Launches the current item if not 'sco'
5502
     * (starts timer and make sure there is a record ready in the DB).
5503
     *
5504
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5505
     *
5506
     * @return bool
5507
     */
5508
    public function start_current_item($allow_new_attempt = false)
5509
    {
5510
        $debug = $this->debug;
5511
        if ($debug) {
5512
            error_log('In learnpath::start_current_item()');
5513
            error_log('current: '.$this->current);
5514
        }
5515
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5516
            $type = $this->get_type();
5517
            $item_type = $this->items[$this->current]->get_type();
5518
            if (($type == 2 && $item_type != 'sco') ||
5519
                ($type == 3 && $item_type != 'au') ||
5520
                (
5521
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5522
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5523
                )
5524
            ) {
5525
                if ($debug) {
5526
                    error_log('item type: '.$item_type);
5527
                    error_log('lp type: '.$type);
5528
                }
5529
                $this->items[$this->current]->open($allow_new_attempt);
5530
                $this->autocomplete_parents($this->current);
5531
                $prereq_check = $this->prerequisites_match($this->current);
5532
                if ($debug) {
5533
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5534
                }
5535
                $this->items[$this->current]->save(false, $prereq_check);
5536
            }
5537
            // If sco, then it is supposed to have been updated by some other call.
5538
            if ($item_type == 'sco') {
5539
                $this->items[$this->current]->restart();
5540
            }
5541
        }
5542
        if ($debug) {
5543
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5544
            error_log('api_get_session_id: '.api_get_session_id());
5545
            error_log('End of learnpath::start_current_item()');
5546
        }
5547
5548
        return true;
5549
    }
5550
5551
    /**
5552
     * Stops the processing and counters for the old item (as held in $this->last).
5553
     *
5554
     * @return bool True/False
5555
     */
5556
    public function stop_previous_item()
5557
    {
5558
        $debug = $this->debug;
5559
        if ($debug) {
5560
            error_log('In learnpath::stop_previous_item()');
5561
        }
5562
5563
        if ($this->last != 0 && $this->last != $this->current &&
5564
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5565
        ) {
5566
            if ($debug) {
5567
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5568
            }
5569
            switch ($this->get_type()) {
5570
                case '3':
5571
                    if ($this->items[$this->last]->get_type() != 'au') {
5572
                        if ($debug) {
5573
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5574
                        }
5575
                        $this->items[$this->last]->close();
5576
                    } else {
5577
                        if ($debug) {
5578
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5579
                        }
5580
                    }
5581
                    break;
5582
                case '2':
5583
                    if ($this->items[$this->last]->get_type() != 'sco') {
5584
                        if ($debug) {
5585
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5586
                        }
5587
                        $this->items[$this->last]->close();
5588
                    } else {
5589
                        if ($debug) {
5590
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5591
                        }
5592
                    }
5593
                    break;
5594
                case '1':
5595
                default:
5596
                    if ($debug) {
5597
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5598
                    }
5599
                    $this->items[$this->last]->close();
5600
                    break;
5601
            }
5602
        } else {
5603
            if ($debug) {
5604
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5605
            }
5606
5607
            return false;
5608
        }
5609
5610
        return true;
5611
    }
5612
5613
    /**
5614
     * Updates the default view mode from fullscreen to embedded and inversely.
5615
     *
5616
     * @return string The current default view mode ('fullscreen' or 'embedded')
5617
     */
5618
    public function update_default_view_mode()
5619
    {
5620
        $table = Database::get_course_table(TABLE_LP_MAIN);
5621
        $sql = "SELECT * FROM $table
5622
                WHERE iid = ".$this->get_id();
5623
        $res = Database::query($sql);
5624
        if (Database::num_rows($res) > 0) {
5625
            $row = Database::fetch_array($res);
5626
            $default_view_mode = $row['default_view_mod'];
5627
            $view_mode = $default_view_mode;
5628
            switch ($default_view_mode) {
5629
                case 'fullscreen': // default with popup
5630
                    $view_mode = 'embedded';
5631
                    break;
5632
                case 'embedded': // default view with left menu
5633
                    $view_mode = 'embedframe';
5634
                    break;
5635
                case 'embedframe': //folded menu
5636
                    $view_mode = 'impress';
5637
                    break;
5638
                case 'impress':
5639
                    $view_mode = 'fullscreen';
5640
                    break;
5641
            }
5642
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5643
                    WHERE iid = ".$this->get_id();
5644
            Database::query($sql);
5645
            $this->mode = $view_mode;
5646
5647
            return $view_mode;
5648
        }
5649
5650
        return -1;
5651
    }
5652
5653
    /**
5654
     * Updates the default behaviour about auto-commiting SCORM updates.
5655
     *
5656
     * @return bool True if auto-commit has been set to 'on', false otherwise
5657
     */
5658
    public function update_default_scorm_commit()
5659
    {
5660
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5661
        $sql = "SELECT * FROM $lp_table
5662
                WHERE iid = ".$this->get_id();
5663
        $res = Database::query($sql);
5664
        if (Database::num_rows($res) > 0) {
5665
            $row = Database::fetch_array($res);
5666
            $force = $row['force_commit'];
5667
            if ($force == 1) {
5668
                $force = 0;
5669
                $force_return = false;
5670
            } elseif ($force == 0) {
5671
                $force = 1;
5672
                $force_return = true;
5673
            }
5674
            $sql = "UPDATE $lp_table SET force_commit = $force
5675
                    WHERE iid = ".$this->get_id();
5676
            Database::query($sql);
5677
            $this->force_commit = $force_return;
5678
5679
            return $force_return;
5680
        }
5681
5682
        return -1;
5683
    }
5684
5685
    /**
5686
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5687
     *
5688
     * @return bool True on success, false on failure
5689
     */
5690
    public function update_display_order()
5691
    {
5692
        $course_id = api_get_course_int_id();
5693
        $table = Database::get_course_table(TABLE_LP_MAIN);
5694
        $sql = "SELECT * FROM $table
5695
                WHERE c_id = $course_id
5696
                ORDER BY display_order";
5697
        $res = Database::query($sql);
5698
        if ($res === false) {
5699
            return false;
5700
        }
5701
5702
        $num = Database::num_rows($res);
5703
        // First check the order is correct, globally (might be wrong because
5704
        // of versions < 1.8.4).
5705
        if ($num > 0) {
5706
            $i = 1;
5707
            while ($row = Database::fetch_array($res)) {
5708
                if ($row['display_order'] != $i) {
5709
                    // If we find a gap in the order, we need to fix it.
5710
                    $sql = "UPDATE $table SET display_order = $i
5711
                            WHERE iid = ".$row['iid'];
5712
                    Database::query($sql);
5713
                }
5714
                $i++;
5715
            }
5716
        }
5717
5718
        return true;
5719
    }
5720
5721
    /**
5722
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5723
     *
5724
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5725
     */
5726
    public function update_reinit()
5727
    {
5728
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5729
        $sql = "SELECT * FROM $lp_table
5730
                WHERE iid = ".$this->get_id();
5731
        $res = Database::query($sql);
5732
        if (Database::num_rows($res) > 0) {
5733
            $row = Database::fetch_array($res);
5734
            $force = $row['prevent_reinit'];
5735
            if ($force == 1) {
5736
                $force = 0;
5737
            } elseif ($force == 0) {
5738
                $force = 1;
5739
            }
5740
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5741
                    WHERE iid = ".$this->get_id();
5742
            Database::query($sql);
5743
            $this->prevent_reinit = $force;
5744
5745
            return $force;
5746
        }
5747
5748
        return -1;
5749
    }
5750
5751
    /**
5752
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5753
     *
5754
     * @return string 'single', 'multi' or 'seriousgame'
5755
     *
5756
     * @author ndiechburg <[email protected]>
5757
     */
5758
    public function get_attempt_mode()
5759
    {
5760
        //Set default value for seriousgame_mode
5761
        if (!isset($this->seriousgame_mode)) {
5762
            $this->seriousgame_mode = 0;
5763
        }
5764
        // Set default value for prevent_reinit
5765
        if (!isset($this->prevent_reinit)) {
5766
            $this->prevent_reinit = 1;
5767
        }
5768
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5769
            return 'seriousgame';
5770
        }
5771
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5772
            return 'single';
5773
        }
5774
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5775
            return 'multiple';
5776
        }
5777
5778
        return 'single';
5779
    }
5780
5781
    /**
5782
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5783
     *
5784
     * @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...
5785
     *
5786
     * @return bool
5787
     *
5788
     * @author ndiechburg <[email protected]>
5789
     */
5790
    public function set_attempt_mode($mode)
5791
    {
5792
        switch ($mode) {
5793
            case 'seriousgame':
5794
                $sg_mode = 1;
5795
                $prevent_reinit = 1;
5796
                break;
5797
            case 'single':
5798
                $sg_mode = 0;
5799
                $prevent_reinit = 1;
5800
                break;
5801
            case 'multiple':
5802
                $sg_mode = 0;
5803
                $prevent_reinit = 0;
5804
                break;
5805
            default:
5806
                $sg_mode = 0;
5807
                $prevent_reinit = 0;
5808
                break;
5809
        }
5810
        $this->prevent_reinit = $prevent_reinit;
5811
        $this->seriousgame_mode = $sg_mode;
5812
        $table = Database::get_course_table(TABLE_LP_MAIN);
5813
        $sql = "UPDATE $table SET
5814
                prevent_reinit = $prevent_reinit ,
5815
                seriousgame_mode = $sg_mode
5816
                WHERE iid = ".$this->get_id();
5817
        $res = Database::query($sql);
5818
        if ($res) {
5819
            return true;
5820
        } else {
5821
            return false;
5822
        }
5823
    }
5824
5825
    /**
5826
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5827
     *
5828
     * @author ndiechburg <[email protected]>
5829
     */
5830
    public function switch_attempt_mode()
5831
    {
5832
        $mode = $this->get_attempt_mode();
5833
        switch ($mode) {
5834
            case 'single':
5835
                $next_mode = 'multiple';
5836
                break;
5837
            case 'multiple':
5838
                $next_mode = 'seriousgame';
5839
                break;
5840
            case 'seriousgame':
5841
            default:
5842
                $next_mode = 'single';
5843
                break;
5844
        }
5845
        $this->set_attempt_mode($next_mode);
5846
    }
5847
5848
    /**
5849
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5850
     * but possibility to do again a completed item.
5851
     *
5852
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5853
     *
5854
     * @author ndiechburg <[email protected]>
5855
     */
5856
    public function set_seriousgame_mode()
5857
    {
5858
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5859
        $sql = "SELECT * FROM $lp_table
5860
                WHERE iid = ".$this->get_id();
5861
        $res = Database::query($sql);
5862
        if (Database::num_rows($res) > 0) {
5863
            $row = Database::fetch_array($res);
5864
            $force = $row['seriousgame_mode'];
5865
            if ($force == 1) {
5866
                $force = 0;
5867
            } elseif ($force == 0) {
5868
                $force = 1;
5869
            }
5870
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5871
			        WHERE iid = ".$this->get_id();
5872
            Database::query($sql);
5873
            $this->seriousgame_mode = $force;
5874
5875
            return $force;
5876
        }
5877
5878
        return -1;
5879
    }
5880
5881
    /**
5882
     * Updates the "scorm_debug" value that shows or hide the debug window.
5883
     *
5884
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5885
     */
5886
    public function update_scorm_debug()
5887
    {
5888
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5889
        $sql = "SELECT * FROM $lp_table
5890
                WHERE iid = ".$this->get_id();
5891
        $res = Database::query($sql);
5892
        if (Database::num_rows($res) > 0) {
5893
            $row = Database::fetch_array($res);
5894
            $force = $row['debug'];
5895
            if ($force == 1) {
5896
                $force = 0;
5897
            } elseif ($force == 0) {
5898
                $force = 1;
5899
            }
5900
            $sql = "UPDATE $lp_table SET debug = $force
5901
                    WHERE iid = ".$this->get_id();
5902
            Database::query($sql);
5903
            $this->scorm_debug = $force;
5904
5905
            return $force;
5906
        }
5907
5908
        return -1;
5909
    }
5910
5911
    /**
5912
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5913
     *
5914
     * @author Kevin Van Den Haute
5915
     *
5916
     * @param  array
5917
     */
5918
    public function tree_array($array)
5919
    {
5920
        $array = $this->sort_tree_array($array);
5921
        $this->create_tree_array($array);
5922
    }
5923
5924
    /**
5925
     * Creates an array with the elements of the learning path tree in it.
5926
     *
5927
     * @author Kevin Van Den Haute
5928
     *
5929
     * @param array $array
5930
     * @param int   $parent
5931
     * @param int   $depth
5932
     * @param array $tmp
5933
     */
5934
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5935
    {
5936
        if (is_array($array)) {
5937
            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...
5938
                if ($array[$i]['parent_item_id'] == $parent) {
5939
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5940
                        $tmp[] = $array[$i]['parent_item_id'];
5941
                        $depth++;
5942
                    }
5943
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5944
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5945
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5946
5947
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5948
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5949
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5950
                    $this->arrMenu[] = [
5951
                        'id' => $array[$i]['id'],
5952
                        'ref' => $ref,
5953
                        'item_type' => $array[$i]['item_type'],
5954
                        'title' => $array[$i]['title'],
5955
                        'title_raw' => $array[$i]['title_raw'],
5956
                        'path' => $path,
5957
                        'description' => $array[$i]['description'],
5958
                        'parent_item_id' => $array[$i]['parent_item_id'],
5959
                        'previous_item_id' => $array[$i]['previous_item_id'],
5960
                        'next_item_id' => $array[$i]['next_item_id'],
5961
                        'min_score' => $array[$i]['min_score'],
5962
                        'max_score' => $array[$i]['max_score'],
5963
                        'mastery_score' => $array[$i]['mastery_score'],
5964
                        'display_order' => $array[$i]['display_order'],
5965
                        'prerequisite' => $preq,
5966
                        'depth' => $depth,
5967
                        'audio' => $audio,
5968
                        'prerequisite_min_score' => $prerequisiteMinScore,
5969
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5970
                    ];
5971
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5972
                }
5973
            }
5974
        }
5975
    }
5976
5977
    /**
5978
     * Sorts a multi dimensional array by parent id and display order.
5979
     *
5980
     * @author Kevin Van Den Haute
5981
     *
5982
     * @param array $array (array with al the learning path items in it)
5983
     *
5984
     * @return array
5985
     */
5986
    public function sort_tree_array($array)
5987
    {
5988
        foreach ($array as $key => $row) {
5989
            $parent[$key] = $row['parent_item_id'];
5990
            $position[$key] = $row['display_order'];
5991
        }
5992
5993
        if (count($array) > 0) {
5994
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5995
        }
5996
5997
        return $array;
5998
    }
5999
6000
    /**
6001
     * Function that creates a html list of learning path items so that we can add audio files to them.
6002
     *
6003
     * @author Kevin Van Den Haute
6004
     *
6005
     * @return string
6006
     */
6007
    public function overview()
6008
    {
6009
        $return = '';
6010
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6011
6012
        // we need to start a form when we want to update all the mp3 files
6013
        if ($update_audio == 'true') {
6014
            $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">';
6015
        }
6016
        $return .= '<div id="message"></div>';
6017
        if (count($this->items) == 0) {
6018
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6019
        } else {
6020
            $return_audio = '<table class="table table-hover table-striped data_table">';
6021
            $return_audio .= '<tr>';
6022
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6023
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6024
            $return_audio .= '</tr>';
6025
6026
            if ($update_audio != 'true') {
6027
                $return .= '<div class="col-md-12">';
6028
                $return .= self::return_new_tree($update_audio);
6029
                $return .= '</div>';
6030
                $return .= Display::div(
6031
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6032
                    ['style' => 'float:left; margin-top:15px;width:100%']
6033
                );
6034
            } else {
6035
                $return_audio .= self::return_new_tree($update_audio);
6036
                $return .= $return_audio.'</table>';
6037
            }
6038
6039
            // We need to close the form when we are updating the mp3 files.
6040
            if ($update_audio == 'true') {
6041
                $return .= '<div class="footer-audio">';
6042
                $return .= Display::button(
6043
                    'save_audio',
6044
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6045
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6046
                );
6047
                $return .= '</div>';
6048
            }
6049
        }
6050
6051
        // We need to close the form when we are updating the mp3 files.
6052
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6053
            $return .= '</form>';
6054
        }
6055
6056
        return $return;
6057
    }
6058
6059
    /**
6060
     * @param string $update_audio
6061
     *
6062
     * @return array
6063
     */
6064
    public function processBuildMenuElements($update_audio = 'false')
6065
    {
6066
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6067
        $arrLP = $this->getItemsForForm();
6068
6069
        $this->tree_array($arrLP);
6070
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6071
        unset($this->arrMenu);
6072
        $default_data = null;
6073
        $default_content = null;
6074
        $elements = [];
6075
        $return_audio = null;
6076
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6077
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6078
        $countItems = count($arrLP);
6079
6080
        $upIcon = Display::return_icon(
6081
            'up.png',
6082
            get_lang('Up'),
6083
            [],
6084
            ICON_SIZE_TINY
6085
        );
6086
6087
        $disableUpIcon = Display::return_icon(
6088
            'up_na.png',
6089
            get_lang('Up'),
6090
            [],
6091
            ICON_SIZE_TINY
6092
        );
6093
6094
        $downIcon = Display::return_icon(
6095
            'down.png',
6096
            get_lang('Down'),
6097
            [],
6098
            ICON_SIZE_TINY
6099
        );
6100
6101
        $disableDownIcon = Display::return_icon(
6102
            'down_na.png',
6103
            get_lang('Down'),
6104
            [],
6105
            ICON_SIZE_TINY
6106
        );
6107
6108
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6109
6110
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6111
        $plugin = null;
6112
        if ($pluginCalendar) {
6113
            $plugin = LearningCalendarPlugin::create();
6114
        }
6115
6116
        for ($i = 0; $i < $countItems; $i++) {
6117
            $parent_id = $arrLP[$i]['parent_item_id'];
6118
            $title = $arrLP[$i]['title'];
6119
            $title_cut = $arrLP[$i]['title_raw'];
6120
            if ($show === false) {
6121
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6122
            }
6123
            // Link for the documents
6124
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6125
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6126
                $title_cut = Display::url(
6127
                    $title_cut,
6128
                    $url,
6129
                    [
6130
                        'class' => 'ajax moved',
6131
                        'data-title' => $title,
6132
                        'title' => $title,
6133
                    ]
6134
                );
6135
            }
6136
6137
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6138
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6139
                Session::write('pathItem', $arrLP[$i]['path']);
6140
            }
6141
6142
            $oddClass = 'row_even';
6143
            if (($i % 2) == 0) {
6144
                $oddClass = 'row_odd';
6145
            }
6146
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6147
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6148
6149
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6150
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6151
            } else {
6152
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6153
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6154
                } else {
6155
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6156
                        $icon = Display::return_icon('certificate.png');
6157
                    } else {
6158
                        $icon = Display::return_icon('folder_document.gif');
6159
                    }
6160
                }
6161
            }
6162
6163
            // The audio column.
6164
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6165
            $audio = '';
6166
            if (!$update_audio || $update_audio != 'true') {
6167
                if (empty($arrLP[$i]['audio'])) {
6168
                    $audio .= '';
6169
                }
6170
            } else {
6171
                $types = self::getChapterTypes();
6172
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6173
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6174
                    if (!empty($arrLP[$i]['audio'])) {
6175
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6176
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6177
                    }
6178
                }
6179
            }
6180
6181
            $return_audio .= Display::span($icon.' '.$title).
6182
                Display::tag(
6183
                    'td',
6184
                    $audio,
6185
                    ['style' => '']
6186
                );
6187
            $return_audio .= '</td>';
6188
            $move_icon = '';
6189
            $move_item_icon = '';
6190
            $edit_icon = '';
6191
            $delete_icon = '';
6192
            $audio_icon = '';
6193
            $prerequisities_icon = '';
6194
            $forumIcon = '';
6195
            $previewIcon = '';
6196
            $pluginCalendarIcon = '';
6197
            $orderIcons = '';
6198
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6199
6200
            if ($is_allowed_to_edit) {
6201
                if (!$update_audio || $update_audio != 'true') {
6202
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6203
                        $move_icon .= '<a class="moved" href="#">';
6204
                        $move_icon .= Display::return_icon(
6205
                            'move_everywhere.png',
6206
                            get_lang('Move'),
6207
                            [],
6208
                            ICON_SIZE_TINY
6209
                        );
6210
                        $move_icon .= '</a>';
6211
                    }
6212
                }
6213
6214
                // No edit for this item types
6215
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6216
                    if ($arrLP[$i]['item_type'] != 'dir') {
6217
                        $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">';
6218
                        $edit_icon .= Display::return_icon(
6219
                            'edit.png',
6220
                            get_lang('LearnpathEditModule'),
6221
                            [],
6222
                            ICON_SIZE_TINY
6223
                        );
6224
                        $edit_icon .= '</a>';
6225
6226
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6227
                            $forumThread = null;
6228
                            if (isset($this->items[$arrLP[$i]['id']])) {
6229
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6230
                                    $this->course_int_id,
6231
                                    $this->lp_session_id
6232
                                );
6233
                            }
6234
                            if ($forumThread) {
6235
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6236
                                        'action' => 'dissociate_forum',
6237
                                        'id' => $arrLP[$i]['id'],
6238
                                        'lp_id' => $this->lp_id,
6239
                                    ]);
6240
                                $forumIcon = Display::url(
6241
                                    Display::return_icon(
6242
                                        'forum.png',
6243
                                        get_lang('DissociateForumToLPItem'),
6244
                                        [],
6245
                                        ICON_SIZE_TINY
6246
                                    ),
6247
                                    $forumIconUrl,
6248
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6249
                                );
6250
                            } else {
6251
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6252
                                        'action' => 'create_forum',
6253
                                        'id' => $arrLP[$i]['id'],
6254
                                        'lp_id' => $this->lp_id,
6255
                                    ]);
6256
                                $forumIcon = Display::url(
6257
                                    Display::return_icon(
6258
                                        'forum.png',
6259
                                        get_lang('AssociateForumToLPItem'),
6260
                                        [],
6261
                                        ICON_SIZE_TINY
6262
                                    ),
6263
                                    $forumIconUrl,
6264
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6265
                                );
6266
                            }
6267
                        }
6268
                    } else {
6269
                        $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">';
6270
                        $edit_icon .= Display::return_icon(
6271
                            'edit.png',
6272
                            get_lang('LearnpathEditModule'),
6273
                            [],
6274
                            ICON_SIZE_TINY
6275
                        );
6276
                        $edit_icon .= '</a>';
6277
                    }
6278
                } else {
6279
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6280
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6281
                        $edit_icon .= Display::return_icon(
6282
                            'edit.png',
6283
                            get_lang('Edit'),
6284
                            [],
6285
                            ICON_SIZE_TINY
6286
                        );
6287
                        $edit_icon .= '</a>';
6288
                    }
6289
                }
6290
6291
                if ($pluginCalendar) {
6292
                    $pluginLink = $pluginUrl.
6293
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6294
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6295
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6296
                    if ($itemInfo && $itemInfo['value'] == 1) {
6297
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6298
                    }
6299
                    $pluginCalendarIcon = Display::url(
6300
                        $iconCalendar,
6301
                        $pluginLink,
6302
                        ['class' => 'btn btn-default']
6303
                    );
6304
                }
6305
6306
                if ($arrLP[$i]['item_type'] != 'final_item') {
6307
                    $orderIcons = Display::url(
6308
                        $upIcon,
6309
                        'javascript:void(0)',
6310
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6311
                    );
6312
                    $orderIcons .= Display::url(
6313
                        $downIcon,
6314
                        'javascript:void(0)',
6315
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6316
                    );
6317
                }
6318
6319
                $delete_icon .= ' <a
6320
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6321
                    onclick="return confirmation(\''.addslashes($title).'\');"
6322
                    class="btn btn-default">';
6323
                $delete_icon .= Display::return_icon(
6324
                    'delete.png',
6325
                    get_lang('LearnpathDeleteModule'),
6326
                    [],
6327
                    ICON_SIZE_TINY
6328
                );
6329
                $delete_icon .= '</a>';
6330
6331
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6332
                $previewImage = Display::return_icon(
6333
                    'preview_view.png',
6334
                    get_lang('Preview'),
6335
                    [],
6336
                    ICON_SIZE_TINY
6337
                );
6338
6339
                switch ($arrLP[$i]['item_type']) {
6340
                    case TOOL_DOCUMENT:
6341
                    case TOOL_LP_FINAL_ITEM:
6342
                    case TOOL_READOUT_TEXT:
6343
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6344
                        $previewIcon = Display::url(
6345
                            $previewImage,
6346
                            $urlPreviewLink,
6347
                            [
6348
                                'target' => '_blank',
6349
                                'class' => 'btn btn-default',
6350
                                'data-title' => $arrLP[$i]['title'],
6351
                                'title' => $arrLP[$i]['title'],
6352
                            ]
6353
                        );
6354
                        break;
6355
                    case TOOL_THREAD:
6356
                    case TOOL_FORUM:
6357
                    case TOOL_QUIZ:
6358
                    case TOOL_STUDENTPUBLICATION:
6359
                    case TOOL_LP_FINAL_ITEM:
6360
                    case TOOL_LINK:
6361
                        $class = 'btn btn-default';
6362
                        $target = '_blank';
6363
                        $link = self::rl_get_resource_link_for_learnpath(
6364
                            $this->course_int_id,
6365
                            $this->lp_id,
6366
                            $arrLP[$i]['id'],
6367
                            0
6368
                        );
6369
                        $previewIcon = Display::url(
6370
                            $previewImage,
6371
                            $link,
6372
                            [
6373
                                'class' => $class,
6374
                                'data-title' => $arrLP[$i]['title'],
6375
                                'title' => $arrLP[$i]['title'],
6376
                                'target' => $target,
6377
                            ]
6378
                        );
6379
                        break;
6380
                    default:
6381
                        $previewIcon = Display::url(
6382
                            $previewImage,
6383
                            $url.'&action=view_item',
6384
                            ['class' => 'btn btn-default', 'target' => '_blank']
6385
                        );
6386
                        break;
6387
                }
6388
6389
                if ($arrLP[$i]['item_type'] != 'dir') {
6390
                    $prerequisities_icon = Display::url(
6391
                        Display::return_icon(
6392
                            'accept.png',
6393
                            get_lang('LearnpathPrerequisites'),
6394
                            [],
6395
                            ICON_SIZE_TINY
6396
                        ),
6397
                        $url.'&action=edit_item_prereq',
6398
                        ['class' => 'btn btn-default']
6399
                    );
6400
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6401
                        $move_item_icon = Display::url(
6402
                            Display::return_icon(
6403
                                'move.png',
6404
                                get_lang('Move'),
6405
                                [],
6406
                                ICON_SIZE_TINY
6407
                            ),
6408
                            $url.'&action=move_item',
6409
                            ['class' => 'btn btn-default']
6410
                        );
6411
                    }
6412
                    $audio_icon = Display::url(
6413
                        Display::return_icon(
6414
                            'audio.png',
6415
                            get_lang('UplUpload'),
6416
                            [],
6417
                            ICON_SIZE_TINY
6418
                        ),
6419
                        $url.'&action=add_audio',
6420
                        ['class' => 'btn btn-default']
6421
                    );
6422
                }
6423
            }
6424
            if ($update_audio != 'true') {
6425
                $row = $move_icon.' '.$icon.
6426
                    Display::span($title_cut).
6427
                    Display::tag(
6428
                        'div',
6429
                        "<div class=\"btn-group btn-group-xs\">
6430
                                    $previewIcon
6431
                                    $audio
6432
                                    $edit_icon
6433
                                    $pluginCalendarIcon
6434
                                    $forumIcon
6435
                                    $prerequisities_icon
6436
                                    $move_item_icon
6437
                                    $audio_icon
6438
                                    $orderIcons
6439
                                    $delete_icon
6440
                                </div>",
6441
                        ['class' => 'btn-toolbar button_actions']
6442
                    );
6443
            } else {
6444
                $row =
6445
                    Display::span($title.$icon).
6446
                    Display::span($audio, ['class' => 'button_actions']);
6447
            }
6448
6449
            $default_data[$arrLP[$i]['id']] = $row;
6450
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6451
6452
            if (empty($parent_id)) {
6453
                $elements[$arrLP[$i]['id']]['data'] = $row;
6454
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6455
            } else {
6456
                $parent_arrays = [];
6457
                if ($arrLP[$i]['depth'] > 1) {
6458
                    // Getting list of parents
6459
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6460
                        foreach ($arrLP as $item) {
6461
                            if ($item['id'] == $parent_id) {
6462
                                if ($item['parent_item_id'] == 0) {
6463
                                    $parent_id = $item['id'];
6464
                                    break;
6465
                                } else {
6466
                                    $parent_id = $item['parent_item_id'];
6467
                                    if (empty($parent_arrays)) {
6468
                                        $parent_arrays[] = intval($item['id']);
6469
                                    }
6470
                                    $parent_arrays[] = $parent_id;
6471
                                    break;
6472
                                }
6473
                            }
6474
                        }
6475
                    }
6476
                }
6477
6478
                if (!empty($parent_arrays)) {
6479
                    $parent_arrays = array_reverse($parent_arrays);
6480
                    $val = '$elements';
6481
                    $x = 0;
6482
                    foreach ($parent_arrays as $item) {
6483
                        if ($x != count($parent_arrays) - 1) {
6484
                            $val .= '["'.$item.'"]["children"]';
6485
                        } else {
6486
                            $val .= '["'.$item.'"]["children"]';
6487
                        }
6488
                        $x++;
6489
                    }
6490
                    $val .= "";
6491
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6492
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6493
                } else {
6494
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6495
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6496
                }
6497
            }
6498
        }
6499
6500
        return [
6501
            'elements' => $elements,
6502
            'default_data' => $default_data,
6503
            'default_content' => $default_content,
6504
            'return_audio' => $return_audio,
6505
        ];
6506
    }
6507
6508
    /**
6509
     * @param string $updateAudio true/false strings
6510
     *
6511
     * @return string
6512
     */
6513
    public function returnLpItemList($updateAudio)
6514
    {
6515
        $result = $this->processBuildMenuElements($updateAudio);
6516
6517
        $html = self::print_recursive(
6518
            $result['elements'],
6519
            $result['default_data'],
6520
            $result['default_content']
6521
        );
6522
6523
        if (!empty($html)) {
6524
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6525
        }
6526
6527
        return $html;
6528
    }
6529
6530
    /**
6531
     * @param string $update_audio
6532
     * @param bool   $drop_element_here
6533
     *
6534
     * @return string
6535
     */
6536
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6537
    {
6538
        $result = $this->processBuildMenuElements($update_audio);
6539
6540
        $list = '<ul id="lp_item_list">';
6541
        $tree = $this->print_recursive(
6542
            $result['elements'],
6543
            $result['default_data'],
6544
            $result['default_content']
6545
        );
6546
6547
        if (!empty($tree)) {
6548
            $list .= $tree;
6549
        } else {
6550
            if ($drop_element_here) {
6551
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6552
            }
6553
        }
6554
        $list .= '</ul>';
6555
6556
        $return = Display::panelCollapse(
6557
            $this->name,
6558
            $list,
6559
            'scorm-list',
6560
            null,
6561
            'scorm-list-accordion',
6562
            'scorm-list-collapse'
6563
        );
6564
6565
        if ($update_audio === 'true') {
6566
            $return = $result['return_audio'];
6567
        }
6568
6569
        return $return;
6570
    }
6571
6572
    /**
6573
     * @param array $elements
6574
     * @param array $default_data
6575
     * @param array $default_content
6576
     *
6577
     * @return string
6578
     */
6579
    public function print_recursive($elements, $default_data, $default_content)
6580
    {
6581
        $return = '';
6582
        foreach ($elements as $key => $item) {
6583
            if (isset($item['load_data']) || empty($item['data'])) {
6584
                $item['data'] = $default_data[$item['load_data']];
6585
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6586
            }
6587
            $sub_list = '';
6588
            if (isset($item['type']) && $item['type'] === 'dir') {
6589
                // empty value
6590
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6591
            }
6592
            if (empty($item['children'])) {
6593
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6594
                $active = null;
6595
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6596
                    $active = 'active';
6597
                }
6598
                $return .= Display::tag(
6599
                    'li',
6600
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6601
                    ['id' => $key, 'class' => 'record li_container']
6602
                );
6603
            } else {
6604
                // Sections
6605
                $data = '';
6606
                if (isset($item['children'])) {
6607
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6608
                }
6609
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6610
                $return .= Display::tag(
6611
                    'li',
6612
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6613
                    ['id' => $key, 'class' => 'record li_container']
6614
                );
6615
            }
6616
        }
6617
6618
        return $return;
6619
    }
6620
6621
    /**
6622
     * This function builds the action menu.
6623
     *
6624
     * @param bool   $returnString           Optional
6625
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6626
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6627
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6628
     * @param string $action
6629
     * @param array  $extraField
6630
     *
6631
     * @return string
6632
     */
6633
    public function build_action_menu(
6634
        $returnString = false,
6635
        $showRequirementButtons = true,
6636
        $isConfigPage = false,
6637
        $allowExpand = true,
6638
        $action = '',
6639
        $extraField = []
6640
    ) {
6641
        $actionsRight = '';
6642
        $lpId = $this->lp_id;
6643
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6644
            $back = Display::url(
6645
                Display::return_icon(
6646
                    'back.png',
6647
                    get_lang('ReturnToLearningPaths'),
6648
                    '',
6649
                    ICON_SIZE_MEDIUM
6650
                ),
6651
                'lp_controller.php?'.api_get_cidreq()
6652
            );
6653
        } else {
6654
            $back = Display::url(
6655
                Display::return_icon(
6656
                    'back.png',
6657
                    get_lang('Back'),
6658
                    '',
6659
                    ICON_SIZE_MEDIUM
6660
                ),
6661
                $extraField['backTo']
6662
            );
6663
        }
6664
6665
        $actionsLeft = $back;
6666
        $actionsLeft .= Display::url(
6667
            Display::return_icon(
6668
                'preview_view.png',
6669
                get_lang('Preview'),
6670
                '',
6671
                ICON_SIZE_MEDIUM
6672
            ),
6673
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6674
                'action' => 'view',
6675
                'lp_id' => $lpId,
6676
                'isStudentView' => 'true',
6677
            ])
6678
        );
6679
6680
        $actionsLeft .= Display::url(
6681
            Display::return_icon(
6682
                'upload_audio.png',
6683
                get_lang('UpdateAllAudioFragments'),
6684
                '',
6685
                ICON_SIZE_MEDIUM
6686
            ),
6687
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6688
                'action' => 'admin_view',
6689
                'lp_id' => $lpId,
6690
                'updateaudio' => 'true',
6691
            ])
6692
        );
6693
6694
        $subscriptionSettings = self::getSubscriptionSettings();
6695
        $request = api_request_uri();
6696
        if (strpos($request, 'edit') === false) {
6697
            $actionsLeft .= Display::url(
6698
                Display::return_icon(
6699
                    'settings.png',
6700
                    get_lang('CourseSettings'),
6701
                    '',
6702
                    ICON_SIZE_MEDIUM
6703
                ),
6704
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6705
                    'action' => 'edit',
6706
                    'lp_id' => $lpId,
6707
                ])
6708
            );
6709
        }
6710
6711
        if ((strpos($request, 'build') === false &&
6712
            strpos($request, 'add_item') === false) ||
6713
            in_array($action, ['add_audio'])
6714
        ) {
6715
            $actionsLeft .= Display::url(
6716
                Display::return_icon(
6717
                    'edit.png',
6718
                    get_lang('Edit'),
6719
                    '',
6720
                    ICON_SIZE_MEDIUM
6721
                ),
6722
                'lp_controller.php?'.http_build_query([
6723
                    'action' => 'build',
6724
                    'lp_id' => $lpId,
6725
                ]).'&'.api_get_cidreq()
6726
            );
6727
        }
6728
6729
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6730
            if ($this->subscribeUsers == 1 &&
6731
                $subscriptionSettings['allow_add_users_to_lp']) {
6732
                $actionsLeft .= Display::url(
6733
                    Display::return_icon(
6734
                        'user.png',
6735
                        get_lang('SubscribeUsersToLp'),
6736
                        '',
6737
                        ICON_SIZE_MEDIUM
6738
                    ),
6739
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6740
                );
6741
            }
6742
        }
6743
6744
        if ($allowExpand) {
6745
            $actionsLeft .= Display::url(
6746
                Display::return_icon(
6747
                    'expand.png',
6748
                    get_lang('Expand'),
6749
                    ['id' => 'expand'],
6750
                    ICON_SIZE_MEDIUM
6751
                ).
6752
                Display::return_icon(
6753
                    'contract.png',
6754
                    get_lang('Collapse'),
6755
                    ['id' => 'contract', 'class' => 'hide'],
6756
                    ICON_SIZE_MEDIUM
6757
                ),
6758
                '#',
6759
                ['role' => 'button', 'id' => 'hide_bar_template']
6760
            );
6761
        }
6762
6763
        if ($showRequirementButtons) {
6764
            $buttons = [
6765
                [
6766
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6767
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6768
                        'action' => 'set_previous_step_as_prerequisite',
6769
                        'lp_id' => $lpId,
6770
                    ]),
6771
                ],
6772
                [
6773
                    'title' => get_lang('ClearAllPrerequisites'),
6774
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6775
                        'action' => 'clear_prerequisites',
6776
                        'lp_id' => $lpId,
6777
                    ]),
6778
                ],
6779
            ];
6780
            $actionsRight = Display::groupButtonWithDropDown(
6781
                get_lang('PrerequisitesOptions'),
6782
                $buttons,
6783
                true
6784
            );
6785
        }
6786
6787
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6788
            $actionsLeft .= Display::url(
6789
                Display::return_icon(
6790
                    'add-groups.png',
6791
                    get_lang('Author'),
6792
                    '',
6793
                    ICON_SIZE_MEDIUM
6794
                ),
6795
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6796
                    'action' => 'author_view',
6797
                    'lp_id' => $lpId,
6798
                ])
6799
            );
6800
        }
6801
6802
        $toolbar = Display::toolbarAction(
6803
            'actions-lp-controller',
6804
            [$actionsLeft, $actionsRight]
6805
        );
6806
6807
        if ($returnString) {
6808
            return $toolbar;
6809
        }
6810
6811
        echo $toolbar;
6812
    }
6813
6814
    /**
6815
     * Creates the default learning path folder.
6816
     *
6817
     * @param array $course
6818
     * @param int   $creatorId
6819
     *
6820
     * @return bool
6821
     */
6822
    public static function generate_learning_path_folder($course, $creatorId = 0)
6823
    {
6824
        // Creating learning_path folder
6825
        $dir = '/learning_path';
6826
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6827
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6828
6829
        $folder = false;
6830
        if (!is_dir($filepath.'/'.$dir)) {
6831
            $folderData = create_unexisting_directory(
6832
                $course,
6833
                $creatorId,
6834
                0,
6835
                null,
6836
                0,
6837
                $filepath,
6838
                $dir,
6839
                get_lang('LearningPaths'),
6840
                0
6841
            );
6842
            if (!empty($folderData)) {
6843
                $folder = true;
6844
            }
6845
        } else {
6846
            $folder = true;
6847
        }
6848
6849
        return $folder;
6850
    }
6851
6852
    /**
6853
     * @param array  $course
6854
     * @param string $lp_name
6855
     * @param int    $creatorId
6856
     *
6857
     * @return array
6858
     */
6859
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6860
    {
6861
        $filepath = '';
6862
        $dir = '/learning_path/';
6863
6864
        if (empty($lp_name)) {
6865
            $lp_name = $this->name;
6866
        }
6867
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6868
        $folder = self::generate_learning_path_folder($course, $creatorId);
6869
6870
        // Limits title size
6871
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6872
        $dir = $dir.$title;
6873
6874
        // Creating LP folder
6875
        $documentId = null;
6876
        if ($folder) {
6877
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6878
            if (!is_dir($filepath.'/'.$dir)) {
6879
                $folderData = create_unexisting_directory(
6880
                    $course,
6881
                    $creatorId,
6882
                    0,
6883
                    0,
6884
                    0,
6885
                    $filepath,
6886
                    $dir,
6887
                    $lp_name
6888
                );
6889
                if (!empty($folderData)) {
6890
                    $folder = true;
6891
                }
6892
6893
                $documentId = $folderData['id'];
6894
            } else {
6895
                $folder = true;
6896
            }
6897
            $dir = $dir.'/';
6898
            if ($folder) {
6899
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6900
            }
6901
        }
6902
6903
        if (empty($documentId)) {
6904
            $dir = api_remove_trailing_slash($dir);
6905
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6906
        }
6907
6908
        $array = [
6909
            'dir' => $dir,
6910
            'filepath' => $filepath,
6911
            'folder' => $folder,
6912
            'id' => $documentId,
6913
        ];
6914
6915
        return $array;
6916
    }
6917
6918
    /**
6919
     * Create a new document //still needs some finetuning.
6920
     *
6921
     * @param array  $courseInfo
6922
     * @param string $content
6923
     * @param string $title
6924
     * @param string $extension
6925
     * @param int    $parentId
6926
     * @param int    $creatorId  creator id
6927
     *
6928
     * @return int
6929
     */
6930
    public function create_document(
6931
        $courseInfo,
6932
        $content = '',
6933
        $title = '',
6934
        $extension = 'html',
6935
        $parentId = 0,
6936
        $creatorId = 0
6937
    ) {
6938
        if (!empty($courseInfo)) {
6939
            $course_id = $courseInfo['real_id'];
6940
        } else {
6941
            $course_id = api_get_course_int_id();
6942
        }
6943
6944
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6945
        $sessionId = api_get_session_id();
6946
6947
        // Generates folder
6948
        $result = $this->generate_lp_folder($courseInfo);
6949
        $dir = $result['dir'];
6950
6951
        if (empty($parentId) || $parentId == '/') {
6952
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6953
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6954
6955
            if ($parentId === '/') {
6956
                $dir = '/';
6957
            }
6958
6959
            // Please, do not modify this dirname formatting.
6960
            if (strstr($dir, '..')) {
6961
                $dir = '/';
6962
            }
6963
6964
            if (!empty($dir[0]) && $dir[0] == '.') {
6965
                $dir = substr($dir, 1);
6966
            }
6967
            if (!empty($dir[0]) && $dir[0] != '/') {
6968
                $dir = '/'.$dir;
6969
            }
6970
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6971
                $dir .= '/';
6972
            }
6973
        } else {
6974
            $parentInfo = DocumentManager::get_document_data_by_id(
6975
                $parentId,
6976
                $courseInfo['code']
6977
            );
6978
            if (!empty($parentInfo)) {
6979
                $dir = $parentInfo['path'].'/';
6980
            }
6981
        }
6982
6983
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6984
        if (!is_dir($filepath)) {
6985
            $dir = '/';
6986
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6987
        }
6988
6989
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6990
        // is already escaped twice when it gets here.
6991
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6992
        if (!empty($title)) {
6993
            $title = api_replace_dangerous_char(stripslashes($title));
6994
        } else {
6995
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6996
        }
6997
6998
        $title = disable_dangerous_file($title);
6999
        $filename = $title;
7000
        $content = !empty($content) ? $content : $_POST['content_lp'];
7001
        $tmp_filename = $filename;
7002
7003
        $i = 0;
7004
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7005
            $tmp_filename = $filename.'_'.++$i;
7006
        }
7007
7008
        $filename = $tmp_filename.'.'.$extension;
7009
        if ($extension == 'html') {
7010
            $content = stripslashes($content);
7011
            $content = str_replace(
7012
                api_get_path(WEB_COURSE_PATH),
7013
                api_get_path(REL_PATH).'courses/',
7014
                $content
7015
            );
7016
7017
            // Change the path of mp3 to absolute.
7018
            // The first regexp deals with :// urls.
7019
            $content = preg_replace(
7020
                "|(flashvars=\"file=)([^:/]+)/|",
7021
                "$1".api_get_path(
7022
                    REL_COURSE_PATH
7023
                ).$courseInfo['path'].'/document/',
7024
                $content
7025
            );
7026
            // The second regexp deals with audio/ urls.
7027
            $content = preg_replace(
7028
                "|(flashvars=\"file=)([^/]+)/|",
7029
                "$1".api_get_path(
7030
                    REL_COURSE_PATH
7031
                ).$courseInfo['path'].'/document/$2/',
7032
                $content
7033
            );
7034
            // For flv player: To prevent edition problem with firefox,
7035
            // we have to use a strange tip (don't blame me please).
7036
            $content = str_replace(
7037
                '</body>',
7038
                '<style type="text/css">body{}</style></body>',
7039
                $content
7040
            );
7041
        }
7042
7043
        if (!file_exists($filepath.$filename)) {
7044
            if ($fp = @fopen($filepath.$filename, 'w')) {
7045
                fputs($fp, $content);
7046
                fclose($fp);
7047
7048
                $file_size = filesize($filepath.$filename);
7049
                $save_file_path = $dir.$filename;
7050
7051
                $document_id = add_document(
7052
                    $courseInfo,
7053
                    $save_file_path,
7054
                    'file',
7055
                    $file_size,
7056
                    $tmp_filename,
7057
                    '',
7058
                    0, //readonly
7059
                    true,
7060
                    null,
7061
                    $sessionId,
7062
                    $creatorId
7063
                );
7064
7065
                if ($document_id) {
7066
                    api_item_property_update(
7067
                        $courseInfo,
7068
                        TOOL_DOCUMENT,
7069
                        $document_id,
7070
                        'DocumentAdded',
7071
                        $creatorId,
7072
                        null,
7073
                        null,
7074
                        null,
7075
                        null,
7076
                        $sessionId
7077
                    );
7078
7079
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7080
                    $new_title = $originalTitle;
7081
7082
                    if ($new_comment || $new_title) {
7083
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7084
                        $ct = '';
7085
                        if ($new_comment) {
7086
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7087
                        }
7088
                        if ($new_title) {
7089
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7090
                        }
7091
7092
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7093
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7094
                        Database::query($sql);
7095
                    }
7096
                }
7097
7098
                return $document_id;
7099
            }
7100
        }
7101
    }
7102
7103
    /**
7104
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7105
     *
7106
     * @param array $_course array
7107
     */
7108
    public function edit_document($_course)
7109
    {
7110
        $course_id = api_get_course_int_id();
7111
        $urlAppend = api_get_configuration_value('url_append');
7112
        // Please, do not modify this dirname formatting.
7113
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7114
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7115
7116
        if (strstr($dir, '..')) {
7117
            $dir = '/';
7118
        }
7119
7120
        if (isset($dir[0]) && $dir[0] == '.') {
7121
            $dir = substr($dir, 1);
7122
        }
7123
7124
        if (isset($dir[0]) && $dir[0] != '/') {
7125
            $dir = '/'.$dir;
7126
        }
7127
7128
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7129
            $dir .= '/';
7130
        }
7131
7132
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7133
        if (!is_dir($filepath)) {
7134
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7135
        }
7136
7137
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7138
7139
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7140
            $document_id = (int) $_POST['path'];
7141
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7142
            if (empty($documentInfo)) {
7143
                // Try with iid
7144
                $table = Database::get_course_table(TABLE_DOCUMENT);
7145
                $sql = "SELECT id, path FROM $table
7146
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7147
                $res_doc = Database::query($sql);
7148
                $row = Database::fetch_array($res_doc);
7149
                if ($row) {
7150
                    $document_id = $row['id'];
7151
                    $documentPath = $row['path'];
7152
                }
7153
            } else {
7154
                $documentPath = $documentInfo['path'];
7155
            }
7156
7157
            $content = stripslashes($_POST['content_lp']);
7158
            $file = $filepath.$documentPath;
7159
7160
            if (!file_exists($file)) {
7161
                return false;
7162
            }
7163
7164
            if ($fp = @fopen($file, 'w')) {
7165
                $content = str_replace(
7166
                    api_get_path(WEB_COURSE_PATH),
7167
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7168
                    $content
7169
                );
7170
                // Change the path of mp3 to absolute.
7171
                // The first regexp deals with :// urls.
7172
                $content = preg_replace(
7173
                    "|(flashvars=\"file=)([^:/]+)/|",
7174
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7175
                    $content
7176
                );
7177
                // The second regexp deals with audio/ urls.
7178
                $content = preg_replace(
7179
                    "|(flashvars=\"file=)([^:/]+)/|",
7180
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7181
                    $content
7182
                );
7183
                fputs($fp, $content);
7184
                fclose($fp);
7185
7186
                $sql = "UPDATE $table_doc SET
7187
                            title='".Database::escape_string($_POST['title'])."'
7188
                        WHERE c_id = $course_id AND id = ".$document_id;
7189
                Database::query($sql);
7190
            }
7191
        }
7192
    }
7193
7194
    /**
7195
     * Displays the selected item, with a panel for manipulating the item.
7196
     *
7197
     * @param int    $item_id
7198
     * @param string $msg
7199
     * @param bool   $show_actions
7200
     *
7201
     * @return string
7202
     */
7203
    public function display_item($item_id, $msg = null, $show_actions = true)
7204
    {
7205
        $course_id = api_get_course_int_id();
7206
        $return = '';
7207
        if (is_numeric($item_id)) {
7208
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7209
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7210
                    WHERE lp.iid = ".intval($item_id);
7211
            $result = Database::query($sql);
7212
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7213
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7214
7215
                // Prevents wrong parent selection for document, see Bug#1251.
7216
                if ($row['item_type'] != 'dir') {
7217
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7218
                }
7219
7220
                if ($show_actions) {
7221
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7222
                }
7223
                $return .= '<div style="padding:10px;">';
7224
7225
                if ($msg != '') {
7226
                    $return .= $msg;
7227
                }
7228
7229
                $return .= '<h3>'.$row['title'].'</h3>';
7230
7231
                switch ($row['item_type']) {
7232
                    case TOOL_THREAD:
7233
                        $link = $this->rl_get_resource_link_for_learnpath(
7234
                            $course_id,
7235
                            $row['lp_id'],
7236
                            $item_id,
7237
                            0
7238
                        );
7239
                        $return .= Display::url(
7240
                            get_lang('GoToThread'),
7241
                            $link,
7242
                            ['class' => 'btn btn-primary']
7243
                        );
7244
                        break;
7245
                    case TOOL_FORUM:
7246
                        $return .= Display::url(
7247
                            get_lang('GoToForum'),
7248
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7249
                            ['class' => 'btn btn-primary']
7250
                        );
7251
                        break;
7252
                    case TOOL_QUIZ:
7253
                        if (!empty($row['path'])) {
7254
                            $exercise = new Exercise();
7255
                            $exercise->read($row['path']);
7256
                            $return .= $exercise->description.'<br />';
7257
                            $return .= Display::url(
7258
                                get_lang('GoToExercise'),
7259
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7260
                                ['class' => 'btn btn-primary']
7261
                            );
7262
                        }
7263
                        break;
7264
                    case TOOL_LP_FINAL_ITEM:
7265
                        $return .= $this->getSavedFinalItem();
7266
                        break;
7267
                    case TOOL_DOCUMENT:
7268
                    case TOOL_READOUT_TEXT:
7269
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7270
                        $sql_doc = "SELECT path FROM $tbl_doc
7271
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7272
                        $result = Database::query($sql_doc);
7273
                        $path_file = Database::result($result, 0, 0);
7274
                        $path_parts = pathinfo($path_file);
7275
                        // TODO: Correct the following naive comparisons.
7276
                        if (in_array($path_parts['extension'], [
7277
                            'html',
7278
                            'txt',
7279
                            'png',
7280
                            'jpg',
7281
                            'JPG',
7282
                            'jpeg',
7283
                            'JPEG',
7284
                            'gif',
7285
                            'swf',
7286
                            'pdf',
7287
                            'htm',
7288
                        ])) {
7289
                            $return .= $this->display_document($row['path'], true, true);
7290
                        }
7291
                        break;
7292
                    case TOOL_HOTPOTATOES:
7293
                        $return .= $this->display_document($row['path'], false, true);
7294
                        break;
7295
                }
7296
                $return .= '</div>';
7297
            }
7298
        }
7299
7300
        return $return;
7301
    }
7302
7303
    /**
7304
     * Shows the needed forms for editing a specific item.
7305
     *
7306
     * @param int $item_id
7307
     *
7308
     * @throws Exception
7309
     * @throws HTML_QuickForm_Error
7310
     *
7311
     * @return string
7312
     */
7313
    public function display_edit_item($item_id, $excludeExtraFields = [])
7314
    {
7315
        $course_id = api_get_course_int_id();
7316
        $return = '';
7317
        $item_id = (int) $item_id;
7318
7319
        if (empty($item_id)) {
7320
            return '';
7321
        }
7322
7323
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7324
        $sql = "SELECT * FROM $tbl_lp_item
7325
                WHERE iid = ".$item_id;
7326
        $res = Database::query($sql);
7327
        $row = Database::fetch_array($res);
7328
        switch ($row['item_type']) {
7329
            case 'dir':
7330
            case 'asset':
7331
            case 'sco':
7332
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7333
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7334
                $return .= $this->display_item_form(
7335
                        $row['item_type'],
7336
                        get_lang('EditCurrentChapter').' :',
7337
                        'edit',
7338
                        $item_id,
7339
                        $row
7340
                    );
7341
            } else {
7342
                $return .= $this->display_item_form(
7343
                        $row['item_type'],
7344
                        get_lang('EditCurrentChapter').' :',
7345
                        'edit_item',
7346
                        $item_id,
7347
                        $row
7348
                    );
7349
            }
7350
                break;
7351
            case TOOL_DOCUMENT:
7352
            case TOOL_READOUT_TEXT:
7353
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7354
                $sql = "SELECT lp.*, doc.path as dir
7355
                        FROM $tbl_lp_item as lp
7356
                        LEFT JOIN $tbl_doc as doc
7357
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7358
                        WHERE
7359
                            doc.c_id = $course_id AND
7360
                            lp.iid = ".$item_id;
7361
                $res_step = Database::query($sql);
7362
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7363
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7364
7365
                if ($row['item_type'] === TOOL_DOCUMENT) {
7366
                    $return .= $this->display_document_form(
7367
                        'edit',
7368
                        $item_id,
7369
                        $row_step,
7370
                        null,
7371
                        $excludeExtraFields
7372
                    );
7373
                }
7374
7375
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7376
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7377
                }
7378
                break;
7379
            case TOOL_LINK:
7380
                $linkId = (int) $row['path'];
7381
                if (!empty($linkId)) {
7382
                    $table = Database::get_course_table(TABLE_LINK);
7383
                    $sql = 'SELECT url FROM '.$table.'
7384
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7385
                    $res_link = Database::query($sql);
7386
                    $row_link = Database::fetch_array($res_link);
7387
                    if (empty($row_link)) {
7388
                        // Try with id
7389
                        $sql = 'SELECT url FROM '.$table.'
7390
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7391
                        $res_link = Database::query($sql);
7392
                        $row_link = Database::fetch_array($res_link);
7393
                    }
7394
7395
                    if (is_array($row_link)) {
7396
                        $row['url'] = $row_link['url'];
7397
                    }
7398
                }
7399
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7400
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7401
                break;
7402
            case TOOL_LP_FINAL_ITEM:
7403
                Session::write('finalItem', true);
7404
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7405
                $sql = "SELECT lp.*, doc.path as dir
7406
                        FROM $tbl_lp_item as lp
7407
                        LEFT JOIN $tbl_doc as doc
7408
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7409
                        WHERE
7410
                            doc.c_id = $course_id AND
7411
                            lp.iid = ".$item_id;
7412
                $res_step = Database::query($sql);
7413
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7414
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7415
                $return .= $this->display_document_form(
7416
                    'edit',
7417
                    $item_id,
7418
                    $row_step,
7419
                    null,
7420
                    $excludeExtraFields
7421
                );
7422
                break;
7423
            case TOOL_QUIZ:
7424
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7425
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7426
                break;
7427
            case TOOL_HOTPOTATOES:
7428
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7429
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7430
                break;
7431
            case TOOL_STUDENTPUBLICATION:
7432
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7433
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7434
                break;
7435
            case TOOL_FORUM:
7436
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7437
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7438
                break;
7439
            case TOOL_THREAD:
7440
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7441
                $return .= $this->display_thread_form('edit', $item_id, $row);
7442
                break;
7443
        }
7444
7445
        return $return;
7446
    }
7447
7448
    /**
7449
     * Function that displays a list with al the resources that
7450
     * could be added to the learning path.
7451
     *
7452
     * @throws Exception
7453
     * @throws HTML_QuickForm_Error
7454
     *
7455
     * @return bool
7456
     */
7457
    public function display_resources()
7458
    {
7459
        $course_code = api_get_course_id();
7460
7461
        // Get all the docs.
7462
        $documents = $this->get_documents(true);
7463
7464
        // Get all the exercises.
7465
        $exercises = $this->get_exercises();
7466
7467
        // Get all the links.
7468
        $links = $this->get_links();
7469
7470
        // Get all the student publications.
7471
        $works = $this->get_student_publications();
7472
7473
        // Get all the forums.
7474
        $forums = $this->get_forums(null, $course_code);
7475
7476
        // Get the final item form (see BT#11048) .
7477
        $finish = $this->getFinalItemForm();
7478
7479
        $headers = [
7480
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7481
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7482
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7483
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7484
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7485
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7486
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7487
        ];
7488
7489
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7490
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7491
7492
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7493
7494
        echo Display::tabs(
7495
            $headers,
7496
            [
7497
                $documents,
7498
                $exercises,
7499
                $links,
7500
                $works,
7501
                $forums,
7502
                $dir,
7503
                $finish,
7504
            ],
7505
            'resource_tab',
7506
            [],
7507
            [],
7508
            $selected
7509
        );
7510
7511
        return true;
7512
    }
7513
7514
    /**
7515
     * Returns the extension of a document.
7516
     *
7517
     * @param string $filename
7518
     *
7519
     * @return string Extension (part after the last dot)
7520
     */
7521
    public function get_extension($filename)
7522
    {
7523
        $explode = explode('.', $filename);
7524
7525
        return $explode[count($explode) - 1];
7526
    }
7527
7528
    /**
7529
     * Displays a document by id.
7530
     *
7531
     * @param int  $id
7532
     * @param bool $show_title
7533
     * @param bool $iframe
7534
     * @param bool $edit_link
7535
     *
7536
     * @return string
7537
     */
7538
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7539
    {
7540
        $_course = api_get_course_info();
7541
        $course_id = api_get_course_int_id();
7542
        $id = (int) $id;
7543
        $return = '';
7544
        $table = Database::get_course_table(TABLE_DOCUMENT);
7545
        $sql_doc = "SELECT * FROM $table
7546
                    WHERE c_id = $course_id AND iid = $id";
7547
        $res_doc = Database::query($sql_doc);
7548
        $row_doc = Database::fetch_array($res_doc);
7549
7550
        // TODO: Add a path filter.
7551
        if ($iframe) {
7552
            $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>';
7553
        } else {
7554
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7555
        }
7556
7557
        return $return;
7558
    }
7559
7560
    /**
7561
     * Return HTML form to add/edit a quiz.
7562
     *
7563
     * @param string $action     Action (add/edit)
7564
     * @param int    $id         Item ID if already exists
7565
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7566
     *
7567
     * @throws Exception
7568
     *
7569
     * @return string HTML form
7570
     */
7571
    public function display_quiz_form(
7572
        $action = 'add',
7573
        $id = 0,
7574
        $extra_info = '',
7575
        $excludeExtraFields = []
7576
    ) {
7577
        $course_id = api_get_course_int_id();
7578
        $id = (int) $id;
7579
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7580
7581
        if ($id != 0 && is_array($extra_info)) {
7582
            $item_title = $extra_info['title'];
7583
            $item_description = $extra_info['description'];
7584
        } elseif (is_numeric($extra_info)) {
7585
            $sql = "SELECT title, description
7586
                    FROM $tbl_quiz
7587
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7588
7589
            $result = Database::query($sql);
7590
            $row = Database::fetch_array($result);
7591
            $item_title = $row['title'];
7592
            $item_description = $row['description'];
7593
        } else {
7594
            $item_title = '';
7595
            $item_description = '';
7596
        }
7597
        $item_title = Security::remove_XSS($item_title);
7598
        $item_description = Security::remove_XSS($item_description);
7599
7600
        $parent = 0;
7601
        if ($id != 0 && is_array($extra_info)) {
7602
            $parent = $extra_info['parent_item_id'];
7603
        }
7604
7605
        $arrLP = $this->getItemsForForm();
7606
        $this->tree_array($arrLP);
7607
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7608
        unset($this->arrMenu);
7609
7610
        $form = new FormValidator(
7611
            'quiz_form',
7612
            'POST',
7613
            $this->getCurrentBuildingModeURL()
7614
        );
7615
        $defaults = [];
7616
7617
        if ($action === 'add') {
7618
            $legend = get_lang('CreateTheExercise');
7619
        } elseif ($action === 'move') {
7620
            $legend = get_lang('MoveTheCurrentExercise');
7621
        } else {
7622
            $legend = get_lang('EditCurrentExecice');
7623
        }
7624
7625
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7626
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7627
        }
7628
7629
        $form->addHeader($legend);
7630
7631
        if ($action != 'move') {
7632
            $this->setItemTitle($form);
7633
            $defaults['title'] = $item_title;
7634
        }
7635
7636
        // Select for Parent item, root or chapter
7637
        $selectParent = $form->addSelect(
7638
            'parent',
7639
            get_lang('Parent'),
7640
            [],
7641
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7642
        );
7643
        $selectParent->addOption($this->name, 0);
7644
7645
        $arrHide = [
7646
            $id,
7647
        ];
7648
        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...
7649
            if ($action != 'add') {
7650
                if (
7651
                    ($arrLP[$i]['item_type'] == 'dir') &&
7652
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7653
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7654
                ) {
7655
                    $selectParent->addOption(
7656
                        $arrLP[$i]['title'],
7657
                        $arrLP[$i]['id'],
7658
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7659
                    );
7660
7661
                    if ($parent == $arrLP[$i]['id']) {
7662
                        $selectParent->setSelected($arrLP[$i]['id']);
7663
                    }
7664
                } else {
7665
                    $arrHide[] = $arrLP[$i]['id'];
7666
                }
7667
            } else {
7668
                if ($arrLP[$i]['item_type'] == 'dir') {
7669
                    $selectParent->addOption(
7670
                        $arrLP[$i]['title'],
7671
                        $arrLP[$i]['id'],
7672
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7673
                    );
7674
7675
                    if ($parent == $arrLP[$i]['id']) {
7676
                        $selectParent->setSelected($arrLP[$i]['id']);
7677
                    }
7678
                }
7679
            }
7680
        }
7681
7682
        if (is_array($arrLP)) {
7683
            reset($arrLP);
7684
        }
7685
7686
        $selectPrevious = $form->addSelect(
7687
            'previous',
7688
            get_lang('Position'),
7689
            [],
7690
            ['id' => 'previous']
7691
        );
7692
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7693
7694
        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...
7695
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7696
                $arrLP[$i]['id'] != $id
7697
            ) {
7698
                $selectPrevious->addOption(
7699
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7700
                    $arrLP[$i]['id']
7701
                );
7702
7703
                if (is_array($extra_info)) {
7704
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7705
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7706
                    }
7707
                } elseif ($action == 'add') {
7708
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7709
                }
7710
            }
7711
        }
7712
7713
        if ($action != 'move') {
7714
            $arrHide = [];
7715
            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...
7716
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7717
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7718
                }
7719
            }
7720
        }
7721
7722
        if ('edit' === $action) {
7723
            $extraField = new ExtraField('lp_item');
7724
            $extraField->addElements($form, $id, $excludeExtraFields);
7725
        }
7726
7727
        if ($action === 'add') {
7728
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7729
        } else {
7730
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7731
        }
7732
7733
        if ($action === 'move') {
7734
            $form->addHidden('title', $item_title);
7735
            $form->addHidden('description', $item_description);
7736
        }
7737
7738
        if (is_numeric($extra_info)) {
7739
            $form->addHidden('path', $extra_info);
7740
        } elseif (is_array($extra_info)) {
7741
            $form->addHidden('path', $extra_info['path']);
7742
        }
7743
7744
        $form->addHidden('type', TOOL_QUIZ);
7745
        $form->addHidden('post_time', time());
7746
        $this->setAuthorLpItem($form);
7747
        $form->setDefaults($defaults);
7748
7749
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7750
    }
7751
7752
    /**
7753
     * Addition of Hotpotatoes tests.
7754
     *
7755
     * @param string $action
7756
     * @param int    $id         Internal ID of the item
7757
     * @param string $extra_info
7758
     *
7759
     * @return string HTML structure to display the hotpotatoes addition formular
7760
     */
7761
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7762
    {
7763
        $course_id = api_get_course_int_id();
7764
        $uploadPath = DIR_HOTPOTATOES;
7765
7766
        if ($id != 0 && is_array($extra_info)) {
7767
            $item_title = stripslashes($extra_info['title']);
7768
            $item_description = stripslashes($extra_info['description']);
7769
        } elseif (is_numeric($extra_info)) {
7770
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7771
7772
            $sql = "SELECT * FROM $TBL_DOCUMENT
7773
                    WHERE
7774
                        c_id = $course_id AND
7775
                        path LIKE '".$uploadPath."/%/%htm%' AND
7776
                        iid = ".(int) $extra_info."
7777
                    ORDER BY iid ASC";
7778
7779
            $res_hot = Database::query($sql);
7780
            $row = Database::fetch_array($res_hot);
7781
7782
            $item_title = $row['title'];
7783
            $item_description = $row['description'];
7784
7785
            if (!empty($row['comment'])) {
7786
                $item_title = $row['comment'];
7787
            }
7788
        } else {
7789
            $item_title = '';
7790
            $item_description = '';
7791
        }
7792
7793
        $parent = 0;
7794
        if ($id != 0 && is_array($extra_info)) {
7795
            $parent = $extra_info['parent_item_id'];
7796
        }
7797
7798
        $arrLP = $this->getItemsForForm();
7799
        $legend = '<legend>';
7800
        if ($action == 'add') {
7801
            $legend .= get_lang('CreateTheExercise');
7802
        } elseif ($action == 'move') {
7803
            $legend .= get_lang('MoveTheCurrentExercise');
7804
        } else {
7805
            $legend .= get_lang('EditCurrentExecice');
7806
        }
7807
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7808
            $legend .= Display:: return_message(
7809
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7810
            );
7811
        }
7812
        $legend .= '</legend>';
7813
7814
        $return = '<form method="POST">';
7815
        $return .= $legend;
7816
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7817
        $return .= '<tr>';
7818
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7819
        $return .= '<td class="input">';
7820
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7821
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7822
        $arrHide = [$id];
7823
7824
        if (count($arrLP) > 0) {
7825
            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...
7826
                if ($action != 'add') {
7827
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7828
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7829
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7830
                    ) {
7831
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7832
                    } else {
7833
                        $arrHide[] = $arrLP[$i]['id'];
7834
                    }
7835
                } else {
7836
                    if ($arrLP[$i]['item_type'] == 'dir') {
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
                    }
7839
                }
7840
            }
7841
            reset($arrLP);
7842
        }
7843
7844
        $return .= '</select>';
7845
        $return .= '</td>';
7846
        $return .= '</tr>';
7847
        $return .= '<tr>';
7848
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7849
        $return .= '<td class="input">';
7850
        $return .= '<select id="previous" name="previous" size="1">';
7851
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7852
7853
        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...
7854
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7855
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7856
                    $selected = 'selected="selected" ';
7857
                } elseif ($action == 'add') {
7858
                    $selected = 'selected="selected" ';
7859
                } else {
7860
                    $selected = '';
7861
                }
7862
7863
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7864
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7865
            }
7866
        }
7867
7868
        $return .= '</select>';
7869
        $return .= '</td>';
7870
        $return .= '</tr>';
7871
7872
        if ($action != 'move') {
7873
            $return .= '<tr>';
7874
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7875
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7876
            $return .= '</tr>';
7877
            $id_prerequisite = 0;
7878
            if (is_array($arrLP) && count($arrLP) > 0) {
7879
                foreach ($arrLP as $key => $value) {
7880
                    if ($value['id'] == $id) {
7881
                        $id_prerequisite = $value['prerequisite'];
7882
                        break;
7883
                    }
7884
                }
7885
7886
                $arrHide = [];
7887
                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...
7888
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7889
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7890
                    }
7891
                }
7892
            }
7893
        }
7894
7895
        $return .= '<tr>';
7896
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7897
            get_lang('SaveHotpotatoes').'</button></td>';
7898
        $return .= '</tr>';
7899
        $return .= '</table>';
7900
7901
        if ($action == 'move') {
7902
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7903
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7904
        }
7905
7906
        if (is_numeric($extra_info)) {
7907
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7908
        } elseif (is_array($extra_info)) {
7909
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7910
        }
7911
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7912
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7913
        $return .= '</form>';
7914
7915
        return $return;
7916
    }
7917
7918
    /**
7919
     * Return the form to display the forum edit/add option.
7920
     *
7921
     * @param string $action
7922
     * @param int    $id         ID of the lp_item if already exists
7923
     * @param string $extra_info
7924
     *
7925
     * @throws Exception
7926
     *
7927
     * @return string HTML form
7928
     */
7929
    public function display_forum_form(
7930
        $action = 'add',
7931
        $id = 0,
7932
        $extra_info = '',
7933
        $excludeExtraFields = []
7934
    ) {
7935
        $course_id = api_get_course_int_id();
7936
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7937
7938
        $item_title = '';
7939
        $item_description = '';
7940
7941
        if ($id != 0 && is_array($extra_info)) {
7942
            $item_title = stripslashes($extra_info['title']);
7943
        } elseif (is_numeric($extra_info)) {
7944
            $sql = "SELECT forum_title as title, forum_comment as comment
7945
                    FROM $tbl_forum
7946
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7947
7948
            $result = Database::query($sql);
7949
            $row = Database::fetch_array($result);
7950
7951
            $item_title = $row['title'];
7952
            $item_description = $row['comment'];
7953
        }
7954
        $parent = 0;
7955
        if ($id != 0 && is_array($extra_info)) {
7956
            $parent = $extra_info['parent_item_id'];
7957
        }
7958
        $arrLP = $this->getItemsForForm();
7959
        $this->tree_array($arrLP);
7960
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7961
        unset($this->arrMenu);
7962
7963
        if ($action == 'add') {
7964
            $legend = get_lang('CreateTheForum');
7965
        } elseif ($action == 'move') {
7966
            $legend = get_lang('MoveTheCurrentForum');
7967
        } else {
7968
            $legend = get_lang('EditCurrentForum');
7969
        }
7970
7971
        $form = new FormValidator(
7972
            'forum_form',
7973
            'POST',
7974
            $this->getCurrentBuildingModeURL()
7975
        );
7976
        $defaults = [];
7977
7978
        $form->addHeader($legend);
7979
7980
        if ($action != 'move') {
7981
            $this->setItemTitle($form);
7982
            $defaults['title'] = $item_title;
7983
        }
7984
7985
        $selectParent = $form->addSelect(
7986
            'parent',
7987
            get_lang('Parent'),
7988
            [],
7989
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7990
        );
7991
        $selectParent->addOption($this->name, 0);
7992
        $arrHide = [
7993
            $id,
7994
        ];
7995
        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...
7996
            if ($action != 'add') {
7997
                if ($arrLP[$i]['item_type'] == 'dir' &&
7998
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7999
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8000
                ) {
8001
                    $selectParent->addOption(
8002
                        $arrLP[$i]['title'],
8003
                        $arrLP[$i]['id'],
8004
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8005
                    );
8006
8007
                    if ($parent == $arrLP[$i]['id']) {
8008
                        $selectParent->setSelected($arrLP[$i]['id']);
8009
                    }
8010
                } else {
8011
                    $arrHide[] = $arrLP[$i]['id'];
8012
                }
8013
            } else {
8014
                if ($arrLP[$i]['item_type'] == 'dir') {
8015
                    $selectParent->addOption(
8016
                        $arrLP[$i]['title'],
8017
                        $arrLP[$i]['id'],
8018
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8019
                    );
8020
8021
                    if ($parent == $arrLP[$i]['id']) {
8022
                        $selectParent->setSelected($arrLP[$i]['id']);
8023
                    }
8024
                }
8025
            }
8026
        }
8027
8028
        if (is_array($arrLP)) {
8029
            reset($arrLP);
8030
        }
8031
8032
        $selectPrevious = $form->addSelect(
8033
            'previous',
8034
            get_lang('Position'),
8035
            [],
8036
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8037
        );
8038
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8039
8040
        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...
8041
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8042
                $arrLP[$i]['id'] != $id
8043
            ) {
8044
                $selectPrevious->addOption(
8045
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8046
                    $arrLP[$i]['id']
8047
                );
8048
8049
                if (isset($extra_info['previous_item_id']) &&
8050
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8051
                ) {
8052
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8053
                } elseif ($action == 'add') {
8054
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8055
                }
8056
            }
8057
        }
8058
8059
        if ($action != 'move') {
8060
            $id_prerequisite = 0;
8061
            if (is_array($arrLP)) {
8062
                foreach ($arrLP as $key => $value) {
8063
                    if ($value['id'] == $id) {
8064
                        $id_prerequisite = $value['prerequisite'];
8065
                        break;
8066
                    }
8067
                }
8068
            }
8069
8070
            $arrHide = [];
8071
            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...
8072
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8073
                    if (isset($extra_info['previous_item_id']) &&
8074
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8075
                    ) {
8076
                        $s_selected_position = $arrLP[$i]['id'];
8077
                    } elseif ($action == 'add') {
8078
                        $s_selected_position = 0;
8079
                    }
8080
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8081
                }
8082
            }
8083
        }
8084
8085
        if ('edit' === $action) {
8086
            $extraField = new ExtraField('lp_item');
8087
            $extraField->addElements($form, $id, $excludeExtraFields);
8088
        }
8089
8090
        if ($action == 'add') {
8091
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8092
        } else {
8093
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8094
        }
8095
8096
        if ($action == 'move') {
8097
            $form->addHidden('title', $item_title);
8098
            $form->addHidden('description', $item_description);
8099
        }
8100
8101
        if (is_numeric($extra_info)) {
8102
            $form->addHidden('path', $extra_info);
8103
        } elseif (is_array($extra_info)) {
8104
            $form->addHidden('path', $extra_info['path']);
8105
        }
8106
        $form->addHidden('type', TOOL_FORUM);
8107
        $form->addHidden('post_time', time());
8108
        $this->setAuthorLpItem($form);
8109
        $form->setDefaults($defaults);
8110
8111
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8112
    }
8113
8114
    /**
8115
     * Return HTML form to add/edit forum threads.
8116
     *
8117
     * @param string $action
8118
     * @param int    $id         Item ID if already exists in learning path
8119
     * @param string $extra_info
8120
     *
8121
     * @throws Exception
8122
     *
8123
     * @return string HTML form
8124
     */
8125
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8126
    {
8127
        $course_id = api_get_course_int_id();
8128
        if (empty($course_id)) {
8129
            return null;
8130
        }
8131
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8132
8133
        $item_title = '';
8134
        $item_description = '';
8135
        if ($id != 0 && is_array($extra_info)) {
8136
            $item_title = stripslashes($extra_info['title']);
8137
        } elseif (is_numeric($extra_info)) {
8138
            $sql = "SELECT thread_title as title FROM $tbl_forum
8139
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8140
8141
            $result = Database::query($sql);
8142
            $row = Database::fetch_array($result);
8143
8144
            $item_title = $row['title'];
8145
            $item_description = '';
8146
        }
8147
8148
        $parent = 0;
8149
        if ($id != 0 && is_array($extra_info)) {
8150
            $parent = $extra_info['parent_item_id'];
8151
        }
8152
8153
        $arrLP = $this->getItemsForForm();
8154
        $this->tree_array($arrLP);
8155
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8156
        unset($this->arrMenu);
8157
8158
        $form = new FormValidator(
8159
            'thread_form',
8160
            'POST',
8161
            $this->getCurrentBuildingModeURL()
8162
        );
8163
        $defaults = [];
8164
8165
        if ($action == 'add') {
8166
            $legend = get_lang('CreateTheForum');
8167
        } elseif ($action == 'move') {
8168
            $legend = get_lang('MoveTheCurrentForum');
8169
        } else {
8170
            $legend = get_lang('EditCurrentForum');
8171
        }
8172
8173
        $form->addHeader($legend);
8174
        $selectParent = $form->addSelect(
8175
            'parent',
8176
            get_lang('Parent'),
8177
            [],
8178
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8179
        );
8180
        $selectParent->addOption($this->name, 0);
8181
8182
        $arrHide = [
8183
            $id,
8184
        ];
8185
8186
        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...
8187
            if ($action != 'add') {
8188
                if (
8189
                    ($arrLP[$i]['item_type'] == 'dir') &&
8190
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8191
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8192
                ) {
8193
                    $selectParent->addOption(
8194
                        $arrLP[$i]['title'],
8195
                        $arrLP[$i]['id'],
8196
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8197
                    );
8198
8199
                    if ($parent == $arrLP[$i]['id']) {
8200
                        $selectParent->setSelected($arrLP[$i]['id']);
8201
                    }
8202
                } else {
8203
                    $arrHide[] = $arrLP[$i]['id'];
8204
                }
8205
            } else {
8206
                if ($arrLP[$i]['item_type'] == 'dir') {
8207
                    $selectParent->addOption(
8208
                        $arrLP[$i]['title'],
8209
                        $arrLP[$i]['id'],
8210
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8211
                    );
8212
8213
                    if ($parent == $arrLP[$i]['id']) {
8214
                        $selectParent->setSelected($arrLP[$i]['id']);
8215
                    }
8216
                }
8217
            }
8218
        }
8219
8220
        if ($arrLP != null) {
8221
            reset($arrLP);
8222
        }
8223
8224
        $selectPrevious = $form->addSelect(
8225
            'previous',
8226
            get_lang('Position'),
8227
            [],
8228
            ['id' => 'previous']
8229
        );
8230
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8231
8232
        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...
8233
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8234
                $selectPrevious->addOption(
8235
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8236
                    $arrLP[$i]['id']
8237
                );
8238
8239
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8240
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8241
                } elseif ($action == 'add') {
8242
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8243
                }
8244
            }
8245
        }
8246
8247
        if ($action != 'move') {
8248
            $this->setItemTitle($form);
8249
            $defaults['title'] = $item_title;
8250
8251
            $id_prerequisite = 0;
8252
            if ($arrLP != null) {
8253
                foreach ($arrLP as $key => $value) {
8254
                    if ($value['id'] == $id) {
8255
                        $id_prerequisite = $value['prerequisite'];
8256
                        break;
8257
                    }
8258
                }
8259
            }
8260
8261
            $arrHide = [];
8262
            $s_selected_position = 0;
8263
            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...
8264
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8265
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8266
                        $s_selected_position = $arrLP[$i]['id'];
8267
                    } elseif ($action == 'add') {
8268
                        $s_selected_position = 0;
8269
                    }
8270
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8271
                }
8272
            }
8273
8274
            $selectPrerequisites = $form->addSelect(
8275
                'prerequisites',
8276
                get_lang('LearnpathPrerequisites'),
8277
                [],
8278
                ['id' => 'prerequisites']
8279
            );
8280
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8281
8282
            foreach ($arrHide as $key => $value) {
8283
                $selectPrerequisites->addOption($value['value'], $key);
8284
8285
                if ($key == $s_selected_position && $action == 'add') {
8286
                    $selectPrerequisites->setSelected($key);
8287
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8288
                    $selectPrerequisites->setSelected($key);
8289
                }
8290
            }
8291
        }
8292
8293
        if ('edit' === $action) {
8294
            $extraField = new ExtraField('lp_item');
8295
            $extraField->addElements($form, $id);
8296
        }
8297
8298
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8299
8300
        if ($action == 'move') {
8301
            $form->addHidden('title', $item_title);
8302
            $form->addHidden('description', $item_description);
8303
        }
8304
8305
        if (is_numeric($extra_info)) {
8306
            $form->addHidden('path', $extra_info);
8307
        } elseif (is_array($extra_info)) {
8308
            $form->addHidden('path', $extra_info['path']);
8309
        }
8310
8311
        $form->addHidden('type', TOOL_THREAD);
8312
        $form->addHidden('post_time', time());
8313
        $this->setAuthorLpItem($form);
8314
        $form->setDefaults($defaults);
8315
8316
        return $form->returnForm();
8317
    }
8318
8319
    /**
8320
     * Return the HTML form to display an item (generally a dir item).
8321
     *
8322
     * @param string $item_type
8323
     * @param string $title
8324
     * @param string $action
8325
     * @param int    $id
8326
     * @param string $extra_info
8327
     *
8328
     * @throws Exception
8329
     * @throws HTML_QuickForm_Error
8330
     *
8331
     * @return string HTML form
8332
     */
8333
    public function display_item_form(
8334
        $item_type,
8335
        $title = '',
8336
        $action = 'add_item',
8337
        $id = 0,
8338
        $extra_info = 'new'
8339
    ) {
8340
        $_course = api_get_course_info();
8341
8342
        global $charset;
8343
8344
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8345
        $item_title = '';
8346
        $item_description = '';
8347
        $item_path_fck = '';
8348
8349
        $parent = 0;
8350
        $previousId = null;
8351
        if ($id != 0 && is_array($extra_info)) {
8352
            $item_title = $extra_info['title'];
8353
            $item_description = $extra_info['description'];
8354
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8355
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8356
            $parent = $extra_info['parent_item_id'];
8357
            $previousId = $extra_info['previous_item_id'];
8358
        }
8359
8360
        if ($extra_info instanceof learnpathItem) {
8361
            $item_title = $extra_info->get_title();
8362
            $item_description = $extra_info->get_description();
8363
            $path = $extra_info->get_path();
8364
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8365
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8366
            $parent = $extra_info->get_parent();
8367
            $previousId = $extra_info->previous;
8368
        }
8369
8370
        $id = (int) $id;
8371
        $sql = "SELECT * FROM $tbl_lp_item
8372
                WHERE
8373
                    lp_id = ".$this->lp_id." AND
8374
                    iid != $id";
8375
8376
        if ($item_type === 'dir') {
8377
            $sql .= " AND parent_item_id = 0";
8378
        }
8379
8380
        $result = Database::query($sql);
8381
        $arrLP = [];
8382
        while ($row = Database::fetch_array($result)) {
8383
            $arrLP[] = [
8384
                'id' => $row['iid'],
8385
                'item_type' => $row['item_type'],
8386
                'title' => $this->cleanItemTitle($row['title']),
8387
                'title_raw' => $row['title'],
8388
                'path' => $row['path'],
8389
                'description' => $row['description'],
8390
                'parent_item_id' => $row['parent_item_id'],
8391
                'previous_item_id' => $row['previous_item_id'],
8392
                'next_item_id' => $row['next_item_id'],
8393
                'max_score' => $row['max_score'],
8394
                'min_score' => $row['min_score'],
8395
                'mastery_score' => $row['mastery_score'],
8396
                'prerequisite' => $row['prerequisite'],
8397
                'display_order' => $row['display_order'],
8398
            ];
8399
        }
8400
8401
        $this->tree_array($arrLP);
8402
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8403
        unset($this->arrMenu);
8404
8405
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8406
8407
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8408
        $defaults['title'] = api_html_entity_decode(
8409
            $item_title,
8410
            ENT_QUOTES,
8411
            $charset
8412
        );
8413
        $defaults['description'] = $item_description;
8414
8415
        $form->addHeader($title);
8416
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8417
        $arrHide[0]['padding'] = 20;
8418
        $charset = api_get_system_encoding();
8419
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
8420
            if ($action != 'add') {
8421
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8422
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8423
                ) {
8424
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8425
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8426
                    if ($parent == $arrLP[$i]['id']) {
8427
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8428
                    }
8429
                }
8430
            } else {
8431
                if ($arrLP[$i]['item_type'] === 'dir') {
8432
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8433
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8434
                    if ($parent == $arrLP[$i]['id']) {
8435
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8436
                    }
8437
                }
8438
            }
8439
        }
8440
8441
        if ($action !== 'move') {
8442
            $this->setItemTitle($form);
8443
        } else {
8444
            $form->addElement('hidden', 'title');
8445
        }
8446
8447
        $parentSelect = $form->addElement(
8448
            'select',
8449
            'parent',
8450
            get_lang('Parent'),
8451
            '',
8452
            [
8453
                'id' => 'idParent',
8454
                'onchange' => 'javascript: load_cbo(this.value);',
8455
            ]
8456
        );
8457
8458
        foreach ($arrHide as $key => $value) {
8459
            $parentSelect->addOption(
8460
                $value['value'],
8461
                $key,
8462
                'style="padding-left:'.$value['padding'].'px;"'
8463
            );
8464
            $lastPosition = $key;
8465
        }
8466
8467
        if (!empty($s_selected_parent)) {
8468
            $parentSelect->setSelected($s_selected_parent);
8469
        }
8470
8471
        if (is_array($arrLP)) {
8472
            reset($arrLP);
8473
        }
8474
8475
        $arrHide = [];
8476
        // POSITION
8477
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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