Passed
Push — 1.11.x ( 1f11a0...595808 )
by Julito
10:16
created

learnpath   F

Complexity

Total Complexity 1936

Size/Duplication

Total Lines 14240
Duplicated Lines 0 %

Importance

Changes 11
Bugs 0 Features 0
Metric Value
eloc 7905
c 11
b 0
f 0
dl 0
loc 14240
rs 0.8
wmc 1936

227 Methods

Rating   Name   Duplication   Size   Complexity  
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
A getCourseCode() 0 3 1
A getItem() 0 7 3
A getLpFromSession() 0 28 5
A getAccumulateScormTime() 0 3 1
A next() 0 22 5
B move_down() 0 54 8
B move_up() 0 51 8
F autocomplete_parents() 0 101 17
A get_common_index_terms_by_prefix() 0 17 3
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
A getProgressBar() 0 5 1
B get_iv_objectives_array() 0 38 6
B getChildrenToc() 0 49 11
A get_author() 0 7 2
A getHideTableOfContents() 0 3 1
A get_type() 0 8 3
A get_maker() 0 7 2
A get_progress_bar() 0 12 1
A get_type_static() 0 16 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A get_preview_image() 0 7 2
B get_progress_bar_text() 0 56 11
A get_teacher_toc_buttons() 0 21 4
B getTOCTree() 0 44 8
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
A getChapterTypes() 0 4 1
B get_preview_image_path() 0 28 7
B get_iv_interactions_array() 0 54 8
A getNameNoTags() 0 3 1
A getProgressFromLpList() 0 32 4
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 7
F is_lp_visible_for_student() 0 138 26
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A get_objectives_count_from_db() 0 16 2
C getParentToc() 0 54 13
D getListArrayToc() 0 67 11
A get_lp_session_id() 0 7 2
A get_toc() 0 18 2
A get_name() 0 7 2
A getStatusCSSClassName() 0 7 2
A get_update_queue() 0 3 1
F get_link() 0 326 56
F prerequisites_match() 0 73 17
A previous() 0 11 1
A open() 0 9 1
A get_user_id() 0 7 2
A has_audio() 0 11 3
B get_view() 0 49 7
D move_item() 0 125 18
A get_view_id() 0 7 2
A toggle_visibility() 0 18 3
A toggleCategoryVisibility() 0 14 2
B update_default_view_mode() 0 33 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 copy() 0 24 1
B getCategoryFromCourseIntoSelect() 0 32 7
A switch_attempt_mode() 0 16 4
F create_document() 0 169 30
A tree_array() 0 4 1
A createCategory() 0 27 3
F get_exercises() 0 162 13
C fixBlockedLinks() 0 64 11
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
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 set_prerequisite() 0 10 1
A display_lp_prerequisites_list() 0 31 5
A set_modified_on() 0 10 1
A categoryIsPublished() 0 24 2
F display_link_form() 0 186 35
F display_document_form() 0 367 76
A getForum() 0 44 3
A set_preview_image() 0 11 1
A getExercisesItems() 0 13 3
B getFinalItemForm() 0 91 4
A getSelectedIcon() 0 10 3
F toggle_publish() 0 135 16
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
F createReadOutText() 0 135 27
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
F display_item_form() 0 239 38
A select_previous_item_id() 0 22 1
A getItemsForForm() 0 39 3
A set_seriousgame_mode() 0 23 4
A returnLpItemList() 0 15 2
A getCategoryId() 0 3 1
B create_tree_array() 0 38 11
B get_links() 0 109 6
B set_terms_by_prefix() 0 68 10
A set_use_max_score() 0 12 1
A create_path() 0 14 5
A set_previous_item() 0 6 2
A getCurrentBuildingModeURL() 0 11 5
A display_document() 0 20 2
B upload_image() 0 40 6
A setSubscribeUsers() 0 10 1
A save_current() 0 32 6
A set_theme() 0 11 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 12 3
B restart() 0 40 6
A set_author() 0 10 1
A cleanItemTitle() 0 5 1
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
B generate_lp_folder() 0 57 8
A getAccumulateWorkTimeTotalCourse() 0 10 1
F display_quiz_form() 0 179 36
A moveUpCategory() 0 9 2
A set_proximity() 0 15 2
C display_item() 0 98 16
B overview() 0 50 9
A set_expired_on() 0 23 3
F display_item_prerequisites_form() 0 183 21
B print_recursive() 0 40 10
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
B display_move_item() 0 52 11
C getCalculateStars() 0 80 12
F display_hotpotatoes_form() 0 155 36
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
A getCategoryByCourse() 0 5 1
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
B get_documents() 0 99 2
C get_forums() 0 111 11
A setAuthorLpItem() 0 27 6
F edit_document() 0 82 17
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 getFinalItem() 0 12 4
A setAccumulateScormTime() 0 11 1
A update_reinit() 0 23 4
A getFinalItemTemplate() 0 3 1
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 217 43
A getAccumulateWorkTimeTotal() 0 11 1
B toggleCategoryPublish() 0 86 9
A generate_learning_path_folder() 0 28 4
A set_hide_toc_frame() 0 15 2
A getCategories() 0 9 1
B get_attempt_mode() 0 21 9
A return_new_tree() 0 34 4
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 30
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 lpHasForum() 0 26 1
F display_student_publication_form() 0 164 31
A setAccumulateWorkTime() 0 14 2
B save_item() 0 43 8
A set_name() 0 32 3
F processBuildMenuElements() 0 441 53

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
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
        [$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
                [$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 (!empty($file)) {
3643
                                if (Link::is_youtube_link($file)) {
3644
                                    $src = Link::get_youtube_video_id($file);
3645
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3646
                                } elseif (Link::isVimeoLink($file)) {
3647
                                    $src = Link::getVimeoLinkId($file);
3648
                                    $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3649
                                } else {
3650
                                    // If the current site is HTTPS and the link is
3651
                                    // HTTP, browsers will refuse opening the link
3652
                                    $urlId = api_get_current_access_url_id();
3653
                                    $url = api_get_access_url($urlId, false);
3654
                                    $protocol = substr($url['url'], 0, 5);
3655
                                    if ($protocol === 'https') {
3656
                                        $linkProtocol = substr($file, 0, 5);
3657
                                        if ($linkProtocol === 'http:') {
3658
                                            //this is the special intervention case
3659
                                            $file = api_get_path(
3660
                                                    WEB_CODE_PATH
3661
                                                ).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3662
                                        }
3663
                                    }
3664
                                }
3665
                            }
3666
                            break;
3667
                        case 'quiz':
3668
                            // Check how much attempts of a exercise exits in lp
3669
                            $lp_item_id = $this->get_current_item_id();
3670
                            $lp_view_id = $this->get_view_id();
3671
3672
                            $prevent_reinit = null;
3673
                            if (isset($this->items[$this->current])) {
3674
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3675
                            }
3676
3677
                            if (empty($provided_toc)) {
3678
                                if ($this->debug > 0) {
3679
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3680
                                }
3681
                                $list = $this->get_toc();
3682
                            } else {
3683
                                if ($this->debug > 0) {
3684
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3685
                                }
3686
                                $list = $provided_toc;
3687
                            }
3688
3689
                            $type_quiz = false;
3690
                            foreach ($list as $toc) {
3691
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3692
                                    $type_quiz = true;
3693
                                }
3694
                            }
3695
3696
                            if ($type_quiz) {
3697
                                $lp_item_id = (int) $lp_item_id;
3698
                                $lp_view_id = (int) $lp_view_id;
3699
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3700
                                        WHERE
3701
                                            c_id = $course_id AND
3702
                                            lp_item_id='".$lp_item_id."' AND
3703
                                            lp_view_id ='".$lp_view_id."' AND
3704
                                            status='completed'";
3705
                                $result = Database::query($sql);
3706
                                $row_count = Database:: fetch_row($result);
3707
                                $count_item_view = (int) $row_count[0];
3708
                                $not_multiple_attempt = 0;
3709
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3710
                                    $not_multiple_attempt = 1;
3711
                                }
3712
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3713
                            }
3714
                            break;
3715
                    }
3716
3717
                    $tmp_array = explode('/', $file);
3718
                    $document_name = $tmp_array[count($tmp_array) - 1];
3719
                    if (strpos($document_name, '_DELETED_')) {
3720
                        $file = 'blank.php?error=document_deleted';
3721
                    }
3722
                    break;
3723
                case 2:
3724
                    if ($this->debug > 2) {
3725
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3726
                    }
3727
3728
                    if ($lp_item_type != 'dir') {
3729
                        // Quite complex here:
3730
                        // We want to make sure 'http://' (and similar) links can
3731
                        // be loaded as is (withouth the Chamilo path in front) but
3732
                        // some contents use this form: resource.htm?resource=http://blablabla
3733
                        // which means we have to find a protocol at the path's start, otherwise
3734
                        // it should not be considered as an external URL.
3735
                        // if ($this->prerequisites_match($item_id)) {
3736
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3737
                            if ($this->debug > 2) {
3738
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3739
                            }
3740
                            // Distant url, return as is.
3741
                            $file = $lp_item_path;
3742
                        } else {
3743
                            if ($this->debug > 2) {
3744
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3745
                            }
3746
                            // Prevent getting untranslatable urls.
3747
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3748
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3749
                            // Prepare the path.
3750
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3751
                            // TODO: Fix this for urls with protocol header.
3752
                            $file = str_replace('//', '/', $file);
3753
                            $file = str_replace(':/', '://', $file);
3754
                            if (substr($lp_path, -1) == '/') {
3755
                                $lp_path = substr($lp_path, 0, -1);
3756
                            }
3757
3758
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3759
                                // if file not found.
3760
                                $decoded = html_entity_decode($lp_item_path);
3761
                                [$decoded] = explode('?', $decoded);
3762
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3763
                                    $file = self::rl_get_resource_link_for_learnpath(
3764
                                        $course_id,
3765
                                        $this->get_id(),
3766
                                        $item_id,
3767
                                        $this->get_view_id()
3768
                                    );
3769
                                    if (empty($file)) {
3770
                                        $file = 'blank.php?error=document_not_found';
3771
                                    } else {
3772
                                        $tmp_array = explode('/', $file);
3773
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3774
                                        if (strpos($document_name, '_DELETED_')) {
3775
                                            $file = 'blank.php?error=document_deleted';
3776
                                        } else {
3777
                                            $file = 'blank.php?error=document_not_found';
3778
                                        }
3779
                                    }
3780
                                } else {
3781
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3782
                                }
3783
                            }
3784
                        }
3785
3786
                        // We want to use parameters if they were defined in the imsmanifest
3787
                        if (strpos($file, 'blank.php') === false) {
3788
                            $lp_item_params = ltrim($lp_item_params, '?');
3789
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3790
                        }
3791
                    } else {
3792
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3793
                    }
3794
                    break;
3795
                case 3:
3796
                    if ($this->debug > 2) {
3797
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3798
                    }
3799
                    // Formatting AICC HACP append URL.
3800
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3801
                    if (!empty($lp_item_params)) {
3802
                        $aicc_append .= $lp_item_params.'&';
3803
                    }
3804
                    if ($lp_item_type != 'dir') {
3805
                        // Quite complex here:
3806
                        // We want to make sure 'http://' (and similar) links can
3807
                        // be loaded as is (withouth the Chamilo path in front) but
3808
                        // some contents use this form: resource.htm?resource=http://blablabla
3809
                        // which means we have to find a protocol at the path's start, otherwise
3810
                        // it should not be considered as an external URL.
3811
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3812
                            if ($this->debug > 2) {
3813
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3814
                            }
3815
                            // Distant url, return as is.
3816
                            $file = $lp_item_path;
3817
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3818
                            /*
3819
                            if (stristr($file,'<servername>') !== false) {
3820
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3821
                            }
3822
                            */
3823
                            if (stripos($file, '<servername>') !== false) {
3824
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3825
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3826
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3827
                            }
3828
3829
                            $file .= $aicc_append;
3830
                        } else {
3831
                            if ($this->debug > 2) {
3832
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3833
                            }
3834
                            // Prevent getting untranslatable urls.
3835
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3836
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3837
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3838
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3839
                            // TODO: Fix this for urls with protocol header.
3840
                            $file = str_replace('//', '/', $file);
3841
                            $file = str_replace(':/', '://', $file);
3842
                            $file .= $aicc_append;
3843
                        }
3844
                    } else {
3845
                        $file = 'lp_content.php?type=dir&'.api_get_cidreq();
3846
                    }
3847
                    break;
3848
                case 4:
3849
                    break;
3850
                default:
3851
                    break;
3852
            }
3853
            // Replace &amp; by & because &amp; will break URL with params
3854
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3855
        }
3856
        if ($this->debug > 2) {
3857
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3858
        }
3859
3860
        return $file;
3861
    }
3862
3863
    /**
3864
     * Gets the latest usable view or generate a new one.
3865
     *
3866
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3867
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3868
     *
3869
     * @return int DB lp_view id
3870
     */
3871
    public function get_view($attempt_num = 0, $userId = null)
3872
    {
3873
        $search = '';
3874
        // Use $attempt_num to enable multi-views management (disabled so far).
3875
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3876
            $search = 'AND view_count = '.$attempt_num;
3877
        }
3878
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3879
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3880
3881
        $course_id = api_get_course_int_id();
3882
        $sessionId = api_get_session_id();
3883
3884
        // Check user ID.
3885
        if (empty($userId)) {
3886
            if (empty($this->get_user_id())) {
3887
                $this->error = 'User ID is empty in learnpath::get_view()';
3888
3889
                return null;
3890
            } else {
3891
                $userId = $this->get_user_id();
3892
            }
3893
        }
3894
3895
        $sql = "SELECT iid, view_count FROM $lp_view_table
3896
        		WHERE
3897
        		    c_id = $course_id AND
3898
        		    lp_id = ".$this->get_id()." AND
3899
        		    user_id = ".$userId." AND
3900
        		    session_id = $sessionId
3901
        		    $search
3902
                ORDER BY view_count DESC";
3903
        $res = Database::query($sql);
3904
        if (Database::num_rows($res) > 0) {
3905
            $row = Database::fetch_array($res);
3906
            $this->lp_view_id = $row['iid'];
3907
        } elseif (!api_is_invitee()) {
3908
            // There is no database record, create one.
3909
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3910
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3911
            Database::query($sql);
3912
            $id = Database::insert_id();
3913
            $this->lp_view_id = $id;
3914
3915
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3916
            Database::query($sql);
3917
        }
3918
3919
        return $this->lp_view_id;
3920
    }
3921
3922
    /**
3923
     * Gets the current view id.
3924
     *
3925
     * @return int View ID (from lp_view)
3926
     */
3927
    public function get_view_id()
3928
    {
3929
        if (!empty($this->lp_view_id)) {
3930
            return (int) $this->lp_view_id;
3931
        }
3932
3933
        return 0;
3934
    }
3935
3936
    /**
3937
     * Gets the update queue.
3938
     *
3939
     * @return array Array containing IDs of items to be updated by JavaScript
3940
     */
3941
    public function get_update_queue()
3942
    {
3943
        return $this->update_queue;
3944
    }
3945
3946
    /**
3947
     * Gets the user ID.
3948
     *
3949
     * @return int User ID
3950
     */
3951
    public function get_user_id()
3952
    {
3953
        if (!empty($this->user_id)) {
3954
            return (int) $this->user_id;
3955
        }
3956
3957
        return false;
3958
    }
3959
3960
    /**
3961
     * Checks if any of the items has an audio element attached.
3962
     *
3963
     * @return bool True or false
3964
     */
3965
    public function has_audio()
3966
    {
3967
        $has = false;
3968
        foreach ($this->items as $i => $item) {
3969
            if (!empty($this->items[$i]->audio)) {
3970
                $has = true;
3971
                break;
3972
            }
3973
        }
3974
3975
        return $has;
3976
    }
3977
3978
    /**
3979
     * Moves an item up and down at its level.
3980
     *
3981
     * @param int    $id        Item to move up and down
3982
     * @param string $direction Direction 'up' or 'down'
3983
     *
3984
     * @return bool|int
3985
     */
3986
    public function move_item($id, $direction)
3987
    {
3988
        $course_id = api_get_course_int_id();
3989
        if (empty($id) || empty($direction)) {
3990
            return false;
3991
        }
3992
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3993
        $sql_sel = "SELECT *
3994
                    FROM $tbl_lp_item
3995
                    WHERE
3996
                        iid = $id
3997
                    ";
3998
        $res_sel = Database::query($sql_sel);
3999
        // Check if elem exists.
4000
        if (Database::num_rows($res_sel) < 1) {
4001
            return false;
4002
        }
4003
        // Gather data.
4004
        $row = Database::fetch_array($res_sel);
4005
        $previous = $row['previous_item_id'];
4006
        $next = $row['next_item_id'];
4007
        $display = $row['display_order'];
4008
        $parent = $row['parent_item_id'];
4009
        $lp = $row['lp_id'];
4010
        // Update the item (switch with previous/next one).
4011
        switch ($direction) {
4012
            case 'up':
4013
                if ($display > 1) {
4014
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4015
                                 WHERE iid = $previous";
4016
                    $res_sel2 = Database::query($sql_sel2);
4017
                    if (Database::num_rows($res_sel2) < 1) {
4018
                        $previous_previous = 0;
4019
                    }
4020
                    // Gather data.
4021
                    $row2 = Database::fetch_array($res_sel2);
4022
                    $previous_previous = $row2['previous_item_id'];
4023
                    // Update previous_previous item (switch "next" with current).
4024
                    if ($previous_previous != 0) {
4025
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4026
                                        next_item_id = $id
4027
                                    WHERE iid = $previous_previous";
4028
                        Database::query($sql_upd2);
4029
                    }
4030
                    // Update previous item (switch with current).
4031
                    if ($previous != 0) {
4032
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4033
                                    next_item_id = $next,
4034
                                    previous_item_id = $id,
4035
                                    display_order = display_order +1
4036
                                    WHERE iid = $previous";
4037
                        Database::query($sql_upd2);
4038
                    }
4039
4040
                    // Update current item (switch with previous).
4041
                    if ($id != 0) {
4042
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4043
                                        next_item_id = $previous,
4044
                                        previous_item_id = $previous_previous,
4045
                                        display_order = display_order-1
4046
                                    WHERE c_id = ".$course_id." AND id = $id";
4047
                        Database::query($sql_upd2);
4048
                    }
4049
                    // Update next item (new previous item).
4050
                    if (!empty($next)) {
4051
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4052
                                     WHERE iid = $next";
4053
                        Database::query($sql_upd2);
4054
                    }
4055
                    $display = $display - 1;
4056
                }
4057
                break;
4058
            case 'down':
4059
                if ($next != 0) {
4060
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4061
                                 WHERE iid = $next";
4062
                    $res_sel2 = Database::query($sql_sel2);
4063
                    if (Database::num_rows($res_sel2) < 1) {
4064
                        $next_next = 0;
4065
                    }
4066
                    // Gather data.
4067
                    $row2 = Database::fetch_array($res_sel2);
4068
                    $next_next = $row2['next_item_id'];
4069
                    // Update previous item (switch with current).
4070
                    if ($previous != 0) {
4071
                        $sql_upd2 = "UPDATE $tbl_lp_item
4072
                                     SET next_item_id = $next
4073
                                     WHERE iid = $previous";
4074
                        Database::query($sql_upd2);
4075
                    }
4076
                    // Update current item (switch with previous).
4077
                    if ($id != 0) {
4078
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4079
                                     previous_item_id = $next,
4080
                                     next_item_id = $next_next,
4081
                                     display_order = display_order + 1
4082
                                     WHERE iid = $id";
4083
                        Database::query($sql_upd2);
4084
                    }
4085
4086
                    // Update next item (new previous item).
4087
                    if ($next != 0) {
4088
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4089
                                     previous_item_id = $previous,
4090
                                     next_item_id = $id,
4091
                                     display_order = display_order-1
4092
                                     WHERE iid = $next";
4093
                        Database::query($sql_upd2);
4094
                    }
4095
4096
                    // Update next_next item (switch "previous" with current).
4097
                    if ($next_next != 0) {
4098
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4099
                                     previous_item_id = $id
4100
                                     WHERE iid = $next_next";
4101
                        Database::query($sql_upd2);
4102
                    }
4103
                    $display = $display + 1;
4104
                }
4105
                break;
4106
            default:
4107
                return false;
4108
        }
4109
4110
        return $display;
4111
    }
4112
4113
    /**
4114
     * Move a LP up (display_order).
4115
     *
4116
     * @param int $lp_id      Learnpath ID
4117
     * @param int $categoryId Category ID
4118
     *
4119
     * @return bool
4120
     */
4121
    public static function move_up($lp_id, $categoryId = 0)
4122
    {
4123
        $courseId = api_get_course_int_id();
4124
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4125
4126
        $categoryCondition = '';
4127
        if (!empty($categoryId)) {
4128
            $categoryId = (int) $categoryId;
4129
            $categoryCondition = " AND category_id = $categoryId";
4130
        }
4131
        $sql = "SELECT * FROM $lp_table
4132
                WHERE c_id = $courseId
4133
                $categoryCondition
4134
                ORDER BY display_order";
4135
        $res = Database::query($sql);
4136
        if ($res === false) {
4137
            return false;
4138
        }
4139
4140
        $lps = [];
4141
        $lp_order = [];
4142
        $num = Database::num_rows($res);
4143
        // First check the order is correct, globally (might be wrong because
4144
        // of versions < 1.8.4)
4145
        if ($num > 0) {
4146
            $i = 1;
4147
            while ($row = Database::fetch_array($res)) {
4148
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4149
                    $sql = "UPDATE $lp_table SET display_order = $i
4150
                            WHERE iid = ".$row['iid'];
4151
                    Database::query($sql);
4152
                }
4153
                $row['display_order'] = $i;
4154
                $lps[$row['iid']] = $row;
4155
                $lp_order[$i] = $row['iid'];
4156
                $i++;
4157
            }
4158
        }
4159
        if ($num > 1) { // If there's only one element, no need to sort.
4160
            $order = $lps[$lp_id]['display_order'];
4161
            if ($order > 1) { // If it's the first element, no need to move up.
4162
                $sql = "UPDATE $lp_table SET display_order = $order
4163
                        WHERE iid = ".$lp_order[$order - 1];
4164
                Database::query($sql);
4165
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4166
                        WHERE iid = $lp_id";
4167
                Database::query($sql);
4168
            }
4169
        }
4170
4171
        return true;
4172
    }
4173
4174
    /**
4175
     * Move a learnpath down (display_order).
4176
     *
4177
     * @param int $lp_id      Learnpath ID
4178
     * @param int $categoryId Category ID
4179
     *
4180
     * @return bool
4181
     */
4182
    public static function move_down($lp_id, $categoryId = 0)
4183
    {
4184
        $courseId = api_get_course_int_id();
4185
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4186
4187
        $categoryCondition = '';
4188
        if (!empty($categoryId)) {
4189
            $categoryId = (int) $categoryId;
4190
            $categoryCondition = " AND category_id = $categoryId";
4191
        }
4192
4193
        $sql = "SELECT * FROM $lp_table
4194
                WHERE c_id = $courseId
4195
                $categoryCondition
4196
                ORDER BY display_order";
4197
        $res = Database::query($sql);
4198
        if ($res === false) {
4199
            return false;
4200
        }
4201
        $lps = [];
4202
        $lp_order = [];
4203
        $num = Database::num_rows($res);
4204
        $max = 0;
4205
        // First check the order is correct, globally (might be wrong because
4206
        // of versions < 1.8.4).
4207
        if ($num > 0) {
4208
            $i = 1;
4209
            while ($row = Database::fetch_array($res)) {
4210
                $max = $i;
4211
                if ($row['display_order'] != $i) {
4212
                    // If we find a gap in the order, we need to fix it.
4213
                    $sql = "UPDATE $lp_table SET display_order = $i
4214
                              WHERE iid = ".$row['iid'];
4215
                    Database::query($sql);
4216
                }
4217
                $row['display_order'] = $i;
4218
                $lps[$row['iid']] = $row;
4219
                $lp_order[$i] = $row['iid'];
4220
                $i++;
4221
            }
4222
        }
4223
        if ($num > 1) { // If there's only one element, no need to sort.
4224
            $order = $lps[$lp_id]['display_order'];
4225
            if ($order < $max) { // If it's the first element, no need to move up.
4226
                $sql = "UPDATE $lp_table SET display_order = $order
4227
                        WHERE iid = ".$lp_order[$order + 1];
4228
                Database::query($sql);
4229
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4230
                        WHERE iid = $lp_id";
4231
                Database::query($sql);
4232
            }
4233
        }
4234
4235
        return true;
4236
    }
4237
4238
    /**
4239
     * Updates learnpath attributes to point to the next element
4240
     * The last part is similar to set_current_item but processing the other way around.
4241
     */
4242
    public function next()
4243
    {
4244
        if ($this->debug > 0) {
4245
            error_log('In learnpath::next()', 0);
4246
        }
4247
        $this->last = $this->get_current_item_id();
4248
        $this->items[$this->last]->save(
4249
            false,
4250
            $this->prerequisites_match($this->last)
4251
        );
4252
        $this->autocomplete_parents($this->last);
4253
        $new_index = $this->get_next_index();
4254
        if ($this->debug > 2) {
4255
            error_log('New index: '.$new_index, 0);
4256
        }
4257
        $this->index = $new_index;
4258
        if ($this->debug > 2) {
4259
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4260
        }
4261
        $this->current = $this->ordered_items[$new_index];
4262
        if ($this->debug > 2) {
4263
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4264
        }
4265
    }
4266
4267
    /**
4268
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4269
     * class, this might be redefined to allow several behaviours depending on the document type.
4270
     *
4271
     * @param int $id Resource ID
4272
     */
4273
    public function open($id)
4274
    {
4275
        // TODO:
4276
        // set the current resource attribute to this resource
4277
        // switch on element type (redefine in child class?)
4278
        // set status for this item to "opened"
4279
        // start timer
4280
        // initialise score
4281
        $this->index = 0; //or = the last item seen (see $this->last)
4282
    }
4283
4284
    /**
4285
     * Check that all prerequisites are fulfilled. Returns true and an
4286
     * empty string on success, returns false
4287
     * and the prerequisite string on error.
4288
     * This function is based on the rules for aicc_script language as
4289
     * described in the SCORM 1.2 CAM documentation page 108.
4290
     *
4291
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4292
     *
4293
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4294
     *              string otherwise
4295
     */
4296
    public function prerequisites_match($itemId = null)
4297
    {
4298
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4299
        if ($allow) {
4300
            if (api_is_allowed_to_edit() ||
4301
                api_is_platform_admin(true) ||
4302
                api_is_drh() ||
4303
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4304
            ) {
4305
                return true;
4306
            }
4307
        }
4308
4309
        $debug = $this->debug;
4310
        if ($debug > 0) {
4311
            error_log('In learnpath::prerequisites_match()');
4312
        }
4313
4314
        if (empty($itemId)) {
4315
            $itemId = $this->current;
4316
        }
4317
4318
        $currentItem = $this->getItem($itemId);
4319
        if ($debug > 0) {
4320
            error_log("Checking item id $itemId");
4321
        }
4322
4323
        if ($currentItem) {
4324
            if ($this->type == 2) {
4325
                // Getting prereq from scorm
4326
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4327
            } else {
4328
                $prereq_string = $currentItem->get_prereq_string();
4329
            }
4330
4331
            if (empty($prereq_string)) {
4332
                if ($debug > 0) {
4333
                    error_log('Found prereq_string is empty return true');
4334
                }
4335
4336
                return true;
4337
            }
4338
4339
            // Clean spaces.
4340
            $prereq_string = str_replace(' ', '', $prereq_string);
4341
            if ($debug > 0) {
4342
                error_log('Found prereq_string: '.$prereq_string, 0);
4343
            }
4344
4345
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4346
            $result = $currentItem->parse_prereq(
4347
                $prereq_string,
4348
                $this->items,
4349
                $this->refs_list,
4350
                $this->get_user_id()
4351
            );
4352
4353
            if ($result === false) {
4354
                $this->set_error_msg($currentItem->prereq_alert);
4355
            }
4356
        } else {
4357
            $result = true;
4358
            if ($debug > 1) {
4359
                error_log('$this->items['.$itemId.'] was not an object');
4360
            }
4361
        }
4362
4363
        if ($debug > 1) {
4364
            error_log('Result: '.$result);
4365
            error_log('End of prerequisites_match(). Error message is now '.$this->error);
4366
        }
4367
4368
        return $result;
4369
    }
4370
4371
    /**
4372
     * Updates learnpath attributes to point to the previous element
4373
     * The last part is similar to set_current_item but processing the other way around.
4374
     */
4375
    public function previous()
4376
    {
4377
        $this->last = $this->get_current_item_id();
4378
        $this->items[$this->last]->save(
4379
            false,
4380
            $this->prerequisites_match($this->last)
4381
        );
4382
        $this->autocomplete_parents($this->last);
4383
        $new_index = $this->get_previous_index();
4384
        $this->index = $new_index;
4385
        $this->current = $this->ordered_items[$new_index];
4386
    }
4387
4388
    /**
4389
     * Publishes a learnpath. This basically means show or hide the learnpath
4390
     * to normal users.
4391
     * Can be used as abstract.
4392
     *
4393
     * @param int $lp_id          Learnpath ID
4394
     * @param int $set_visibility New visibility
4395
     *
4396
     * @return bool
4397
     */
4398
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4399
    {
4400
        if (empty($lp_id)) {
4401
            return false;
4402
        }
4403
4404
        $action = 'visible';
4405
        if ($set_visibility != 1) {
4406
            $action = 'invisible';
4407
            self::toggle_publish($lp_id, 'i');
4408
        }
4409
4410
        return api_item_property_update(
4411
            api_get_course_info(),
4412
            TOOL_LEARNPATH,
4413
            $lp_id,
4414
            $action,
4415
            api_get_user_id()
4416
        );
4417
    }
4418
4419
    /**
4420
     * Publishes a learnpath category.
4421
     * This basically means show or hide the learnpath category to normal users.
4422
     *
4423
     * @param int $id
4424
     * @param int $visibility
4425
     *
4426
     * @return bool
4427
     */
4428
    public static function toggleCategoryVisibility($id, $visibility = 1)
4429
    {
4430
        $action = 'visible';
4431
        if ($visibility != 1) {
4432
            self::toggleCategoryPublish($id, 0);
4433
            $action = 'invisible';
4434
        }
4435
4436
        return api_item_property_update(
4437
            api_get_course_info(),
4438
            TOOL_LEARNPATH_CATEGORY,
4439
            $id,
4440
            $action,
4441
            api_get_user_id()
4442
        );
4443
    }
4444
4445
    /**
4446
     * Publishes a learnpath. This basically means show or hide the learnpath
4447
     * on the course homepage
4448
     * Can be used as abstract.
4449
     *
4450
     * @param int    $lp_id          Learnpath id
4451
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4452
     *
4453
     * @return bool
4454
     */
4455
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4456
    {
4457
        if (empty($lp_id)) {
4458
            return false;
4459
        }
4460
        $course_id = api_get_course_int_id();
4461
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4462
        $lp_id = (int) $lp_id;
4463
        $sql = "SELECT * FROM $tbl_lp
4464
                WHERE iid = $lp_id";
4465
        $result = Database::query($sql);
4466
        if (Database::num_rows($result)) {
4467
            $row = Database::fetch_array($result);
4468
            $name = Database::escape_string($row['name']);
4469
            if ($set_visibility === 'i') {
4470
                $v = 0;
4471
            }
4472
            if ($set_visibility === 'v') {
4473
                $v = 1;
4474
            }
4475
4476
            $session_id = api_get_session_id();
4477
            $session_condition = api_get_session_condition($session_id);
4478
4479
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4480
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4481
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4482
4483
            $extraLpCondition = '';
4484
            $extraLink = '';
4485
            if (!empty($session_id)) {
4486
                $extraLink = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session=0';
4487
                $extraLpCondition = " OR (link = '$extraLink' AND session_id = $session_id  )  ";
4488
            }
4489
4490
            $sql = "SELECT * FROM $tbl_tool
4491
                    WHERE
4492
                        c_id = $course_id AND
4493
                        (link = '$link' OR link = '$oldLink' $extraLpCondition ) AND
4494
                        image = 'scormbuilder.gif' AND
4495
                        (
4496
                            link LIKE '$link%' OR
4497
                            link LIKE '$oldLink%'
4498
                            $extraLpCondition
4499
                        )
4500
                        $session_condition
4501
                    ";
4502
4503
            $result = Database::query($sql);
4504
            $num = Database::num_rows($result);
4505
4506
            if ($set_visibility === 'i') {
4507
                if ($num > 0) {
4508
                    $sql = "DELETE FROM $tbl_tool
4509
                            WHERE
4510
                                c_id = $course_id AND
4511
                                (link = '$link' OR link = '$oldLink') AND
4512
                                image='scormbuilder.gif'
4513
                                $session_condition";
4514
                    Database::query($sql);
4515
                }
4516
4517
                // Disables the base course link inside a session.
4518
                if (!empty($session_id) && 0 === (int) $row['session_id']) {
4519
                    $sql = "SELECT iid FROM $tbl_tool
4520
                            WHERE
4521
                                c_id = $course_id AND
4522
                                link = '$extraLink' AND
4523
                                image = 'scormbuilder.gif' AND
4524
                                session_id = $session_id
4525
                    ";
4526
                    $resultBaseLp = Database::query($sql);
4527
                    if (Database::num_rows($resultBaseLp)) {
4528
                        $resultBaseLpRow = Database::fetch_array($resultBaseLp);
4529
                        $id = $resultBaseLpRow['iid'];
4530
                        $sql = "UPDATE $tbl_tool
4531
                                SET visibility = 0
4532
                                WHERE iid = $id ";
4533
                        Database::query($sql);
4534
                    } else {
4535
                        $params = [
4536
                            'category' => 'authoring',
4537
                            'c_id' => $course_id,
4538
                            'name' => $name,
4539
                            'link' => $extraLink,
4540
                            'image' => 'scormbuilder.gif',
4541
                            'visibility' => '0',
4542
                            'admin' => '0',
4543
                            'address' => 'pastillegris.gif',
4544
                            'added_tool' => '0',
4545
                            'session_id' => $session_id,
4546
                        ];
4547
                        $insertId = Database::insert($tbl_tool, $params);
4548
                        if ($insertId) {
4549
                            $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4550
                            Database::query($sql);
4551
                        }
4552
                    }
4553
                }
4554
            }
4555
4556
            if ($set_visibility === 'v') {
4557
                if ($num == 0) {
4558
                    $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id)
4559
                            VALUES ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4560
                    Database::query($sql);
4561
                    $insertId = Database::insert_id();
4562
                    if ($insertId) {
4563
                        $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4564
                        Database::query($sql);
4565
                    }
4566
                }
4567
                if ($num > 0) {
4568
                    $sql = "UPDATE $tbl_tool SET
4569
                        c_id = $course_id,
4570
                        name = '$name',
4571
                        link = '$link',
4572
                        image = 'scormbuilder.gif',
4573
                        visibility = '$v',
4574
                        admin = '0',
4575
                        address = 'pastillegris.gif',
4576
                        added_tool = 0,
4577
                        session_id = $session_id
4578
                    WHERE
4579
                        c_id = ".$course_id." AND
4580
                        (link = '$link' OR link = '$oldLink') AND
4581
                        image='scormbuilder.gif'
4582
                        $session_condition
4583
                    ";
4584
                    Database::query($sql);
4585
                }
4586
            }
4587
        }
4588
4589
        return false;
4590
    }
4591
4592
    /**
4593
     * Publishes a learnpath.
4594
     * Show or hide the learnpath category on the course homepage.
4595
     *
4596
     * @param int $id
4597
     * @param int $setVisibility
4598
     *
4599
     * @return bool
4600
     */
4601
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4602
    {
4603
        $courseId = api_get_course_int_id();
4604
        $sessionId = api_get_session_id();
4605
        $sessionCondition = api_get_session_condition(
4606
            $sessionId,
4607
            true,
4608
            false,
4609
            't.sessionId'
4610
        );
4611
4612
        $em = Database::getManager();
4613
        $category = self::getCategory($id);
4614
4615
        if (!$category) {
4616
            return false;
4617
        }
4618
4619
        if (empty($courseId)) {
4620
            return false;
4621
        }
4622
4623
        $link = self::getCategoryLinkForTool($id);
4624
4625
        /** @var CTool $tool */
4626
        $tool = $em->createQuery("
4627
                SELECT t FROM ChamiloCourseBundle:CTool t
4628
                WHERE
4629
                    t.cId = :course AND
4630
                    t.link = :link1 AND
4631
                    t.image = 'lp_category.gif' AND
4632
                    t.link LIKE :link2
4633
                    $sessionCondition
4634
            ")
4635
            ->setParameters([
4636
                'course' => $courseId,
4637
                'link1' => $link,
4638
                'link2' => "$link%",
4639
            ])
4640
            ->getOneOrNullResult();
4641
4642
        if ($setVisibility == 0 && $tool) {
4643
            $em->remove($tool);
4644
            $em->flush();
4645
4646
            return true;
4647
        }
4648
4649
        if ($setVisibility == 1 && !$tool) {
4650
            $tool = new CTool();
4651
            $tool
4652
                ->setCategory('authoring')
4653
                ->setCId($courseId)
4654
                ->setName(strip_tags($category->getName()))
4655
                ->setLink($link)
4656
                ->setImage('lp_category.gif')
4657
                ->setVisibility(1)
4658
                ->setAdmin(0)
4659
                ->setAddress('pastillegris.gif')
4660
                ->setAddedTool(0)
4661
                ->setSessionId($sessionId)
4662
                ->setTarget('_self');
4663
4664
            $em->persist($tool);
4665
            $em->flush();
4666
4667
            $tool->setId($tool->getIid());
4668
4669
            $em->persist($tool);
4670
            $em->flush();
4671
4672
            return true;
4673
        }
4674
4675
        if ($setVisibility == 1 && $tool) {
4676
            $tool
4677
                ->setName(strip_tags($category->getName()))
4678
                ->setVisibility(1);
4679
4680
            $em->persist($tool);
4681
            $em->flush();
4682
4683
            return true;
4684
        }
4685
4686
        return false;
4687
    }
4688
4689
    /**
4690
     * Check if the learnpath category is visible for a user.
4691
     *
4692
     * @param int
4693
     * @param int
4694
     *
4695
     * @return bool
4696
     */
4697
    public static function categoryIsVisibleForStudent(
4698
        CLpCategory $category,
4699
        User $user,
4700
        $courseId = 0,
4701
        $sessionId = 0
4702
    ) {
4703
        if (empty($category)) {
4704
            return false;
4705
        }
4706
4707
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4708
4709
        if ($isAllowedToEdit) {
4710
            return true;
4711
        }
4712
4713
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4714
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4715
4716
        $courseInfo = api_get_course_info_by_id($courseId);
4717
4718
        $categoryVisibility = api_get_item_visibility(
4719
            $courseInfo,
4720
            TOOL_LEARNPATH_CATEGORY,
4721
            $category->getId(),
4722
            $sessionId
4723
        );
4724
4725
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4726
            return false;
4727
        }
4728
4729
        $subscriptionSettings = self::getSubscriptionSettings();
4730
4731
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4732
            return true;
4733
        }
4734
4735
        $noUserSubscribed = false;
4736
        $noGroupSubscribed = true;
4737
        $users = $category->getUsers();
4738
        if (empty($users) || !$users->count()) {
4739
            $noUserSubscribed = true;
4740
        } elseif ($category->hasUserAdded($user)) {
4741
            return true;
4742
        }
4743
4744
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4745
        $em = Database::getManager();
4746
4747
        /** @var ItemPropertyRepository $itemRepo */
4748
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4749
4750
        /** @var CourseRepository $courseRepo */
4751
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4752
        $session = null;
4753
        if (!empty($sessionId)) {
4754
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4755
        }
4756
4757
        $course = $courseRepo->find($courseId);
4758
4759
        if ($courseId != 0) {
4760
            // Subscribed groups to a LP
4761
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4762
                TOOL_LEARNPATH_CATEGORY,
4763
                $category->getId(),
4764
                $course,
4765
                $session
4766
            );
4767
        }
4768
4769
        if (!empty($subscribedGroupsInLp)) {
4770
            $noGroupSubscribed = false;
4771
            if (!empty($groups)) {
4772
                $groups = array_column($groups, 'iid');
4773
                /** @var CItemProperty $item */
4774
                foreach ($subscribedGroupsInLp as $item) {
4775
                    if ($item->getGroup() &&
4776
                        in_array($item->getGroup()->getId(), $groups)
4777
                    ) {
4778
                        return true;
4779
                    }
4780
                }
4781
            }
4782
        }
4783
        $response = $noGroupSubscribed && $noUserSubscribed;
4784
4785
        return $response;
4786
    }
4787
4788
    /**
4789
     * Check if a learnpath category is published as course tool.
4790
     *
4791
     * @param int $courseId
4792
     *
4793
     * @return bool
4794
     */
4795
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4796
    {
4797
        $link = self::getCategoryLinkForTool($category->getId());
4798
        $em = Database::getManager();
4799
4800
        $tools = $em
4801
            ->createQuery("
4802
                SELECT t FROM ChamiloCourseBundle:CTool t
4803
                WHERE t.cId = :course AND
4804
                    t.name = :name AND
4805
                    t.image = 'lp_category.gif' AND
4806
                    t.link LIKE :link
4807
            ")
4808
            ->setParameters([
4809
                'course' => $courseId,
4810
                'name' => strip_tags($category->getName()),
4811
                'link' => "$link%",
4812
            ])
4813
            ->getResult();
4814
4815
        /** @var CTool $tool */
4816
        $tool = current($tools);
4817
4818
        return $tool ? $tool->getVisibility() : false;
4819
    }
4820
4821
    /**
4822
     * Restart the whole learnpath. Return the URL of the first element.
4823
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4824
     * To use a similar method  statically, use the create_new_attempt() method.
4825
     *
4826
     * @return bool
4827
     */
4828
    public function restart()
4829
    {
4830
        if ($this->debug > 0) {
4831
            error_log('In learnpath::restart()', 0);
4832
        }
4833
        // TODO
4834
        // Call autosave method to save the current progress.
4835
        //$this->index = 0;
4836
        if (api_is_invitee()) {
4837
            return false;
4838
        }
4839
        $session_id = api_get_session_id();
4840
        $course_id = api_get_course_int_id();
4841
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4842
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4843
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4844
        if ($this->debug > 2) {
4845
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4846
        }
4847
        Database::query($sql);
4848
        $view_id = Database::insert_id();
4849
4850
        if ($view_id) {
4851
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4852
            Database::query($sql);
4853
            $this->lp_view_id = $view_id;
4854
            $this->attempt = $this->attempt + 1;
4855
        } else {
4856
            $this->error = 'Could not insert into item_view table...';
4857
4858
            return false;
4859
        }
4860
        $this->autocomplete_parents($this->current);
4861
        foreach ($this->items as $index => $dummy) {
4862
            $this->items[$index]->restart();
4863
            $this->items[$index]->set_lp_view($this->lp_view_id);
4864
        }
4865
        $this->first();
4866
4867
        return true;
4868
    }
4869
4870
    /**
4871
     * Saves the current item.
4872
     *
4873
     * @return bool
4874
     */
4875
    public function save_current()
4876
    {
4877
        $debug = $this->debug;
4878
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4879
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4880
        if ($debug) {
4881
            error_log('save_current() saving item '.$this->current, 0);
4882
            error_log(''.print_r($this->items, true), 0);
4883
        }
4884
        if (isset($this->items[$this->current]) &&
4885
            is_object($this->items[$this->current])
4886
        ) {
4887
            if ($debug) {
4888
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4889
            }
4890
4891
            $res = $this->items[$this->current]->save(
4892
                false,
4893
                $this->prerequisites_match($this->current)
4894
            );
4895
            $this->autocomplete_parents($this->current);
4896
            $status = $this->items[$this->current]->get_status();
4897
            $this->update_queue[$this->current] = $status;
4898
4899
            if ($debug) {
4900
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4901
            }
4902
4903
            return $res;
4904
        }
4905
4906
        return false;
4907
    }
4908
4909
    /**
4910
     * Saves the given item.
4911
     *
4912
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4913
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4914
     *
4915
     * @return bool
4916
     */
4917
    public function save_item($item_id = null, $from_outside = true)
4918
    {
4919
        $debug = $this->debug;
4920
        if ($debug) {
4921
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4922
        }
4923
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4924
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4925
        if (empty($item_id)) {
4926
            $item_id = (int) $_REQUEST['id'];
4927
        }
4928
4929
        if (empty($item_id)) {
4930
            $item_id = $this->get_current_item_id();
4931
        }
4932
        if (isset($this->items[$item_id]) &&
4933
            is_object($this->items[$item_id])
4934
        ) {
4935
            // Saving the item.
4936
            $res = $this->items[$item_id]->save(
4937
                $from_outside,
4938
                $this->prerequisites_match($item_id)
4939
            );
4940
4941
            if ($debug) {
4942
                error_log('update_queue before:');
4943
                error_log(print_r($this->update_queue, 1));
4944
            }
4945
            $this->autocomplete_parents($item_id);
4946
4947
            $status = $this->items[$item_id]->get_status();
4948
            $this->update_queue[$item_id] = $status;
4949
4950
            if ($debug) {
4951
                error_log('get_status(): '.$status);
4952
                error_log('update_queue after:');
4953
                error_log(print_r($this->update_queue, 1));
4954
            }
4955
4956
            return $res;
4957
        }
4958
4959
        return false;
4960
    }
4961
4962
    /**
4963
     * Saves the last item seen's ID only in case.
4964
     */
4965
    public function save_last($score = null)
4966
    {
4967
        $course_id = api_get_course_int_id();
4968
        $debug = $this->debug;
4969
        if ($debug) {
4970
            error_log('In learnpath::save_last()', 0);
4971
        }
4972
        $session_condition = api_get_session_condition(
4973
            api_get_session_id(),
4974
            true,
4975
            false
4976
        );
4977
        $table = Database::get_course_table(TABLE_LP_VIEW);
4978
4979
        $userId = $this->get_user_id();
4980
        if (empty($userId)) {
4981
            $userId = api_get_user_id();
4982
            if ($debug) {
4983
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4984
            }
4985
        }
4986
        if (isset($this->current) && !api_is_invitee()) {
4987
            if ($debug) {
4988
                error_log('Saving current item ('.$this->current.') for later review', 0);
4989
            }
4990
            $sql = "UPDATE $table SET
4991
                        last_item = ".$this->get_current_item_id()."
4992
                    WHERE
4993
                        c_id = $course_id AND
4994
                        lp_id = ".$this->get_id()." AND
4995
                        user_id = ".$userId." ".$session_condition;
4996
            if ($debug) {
4997
                error_log('Saving last item seen : '.$sql, 0);
4998
            }
4999
            Database::query($sql);
5000
        }
5001
5002
        if (!api_is_invitee()) {
5003
            // Save progress.
5004
            [$progress] = $this->get_progress_bar_text('%');
5005
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
5006
            $scoreAsProgress = $this->getUseScoreAsProgress();
5007
            if ($scoreAsProgress && $scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
5008
                if ($debug) {
5009
                    error_log("Return false: Dont save score: $score");
5010
                    error_log("progress: $progress");
5011
                }
5012
5013
                return false;
5014
            }
5015
5016
            if ($scoreAsProgress && $scoreAsProgressSetting) {
5017
                $storedProgress = self::getProgress(
5018
                    $this->get_id(),
5019
                    $userId,
5020
                    $course_id,
5021
                    $this->get_lp_session_id()
5022
                );
5023
5024
                // Check if the stored progress is higher than the new value
5025
                if ($storedProgress >= $progress) {
5026
                    if ($debug) {
5027
                        error_log("Return false: New progress value is lower than stored value - Current value: $storedProgress - New value: $progress [lp ".$this->get_id()." - user ".$userId."]");
5028
                    }
5029
5030
                    return false;
5031
                }
5032
            }
5033
5034
            if ($progress >= 0 && $progress <= 100) {
5035
                // Check database.
5036
                $progress = (int) $progress;
5037
                $sql = "UPDATE $table SET
5038
                            progress = $progress
5039
                        WHERE
5040
                            c_id = $course_id AND
5041
                            lp_id = ".$this->get_id()." AND
5042
                            user_id = ".$userId." ".$session_condition;
5043
                // Ignore errors as some tables might not have the progress field just yet.
5044
                Database::query($sql);
5045
                if ($debug) {
5046
                    error_log($sql);
5047
                }
5048
                $this->progress_db = $progress;
5049
5050
                if (100 == $progress) {
5051
                    HookLearningPathEnd::create()
5052
                        ->setEventData(['lp_view_id' => $this->lp_view_id])
5053
                        ->hookLearningPathEnd();
5054
                }
5055
            }
5056
        }
5057
    }
5058
5059
    /**
5060
     * Sets the current item ID (checks if valid and authorized first).
5061
     *
5062
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
5063
     */
5064
    public function set_current_item($item_id = null)
5065
    {
5066
        $debug = $this->debug;
5067
        if ($debug) {
5068
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
5069
        }
5070
        if (empty($item_id)) {
5071
            if ($debug) {
5072
                error_log('No new current item given, ignore...', 0);
5073
            }
5074
            // Do nothing.
5075
        } else {
5076
            if ($debug) {
5077
                error_log('New current item given is '.$item_id.'...', 0);
5078
            }
5079
            if (is_numeric($item_id)) {
5080
                $item_id = (int) $item_id;
5081
                // TODO: Check in database here.
5082
                $this->last = $this->current;
5083
                $this->current = $item_id;
5084
                // TODO: Update $this->index as well.
5085
                foreach ($this->ordered_items as $index => $item) {
5086
                    if ($item == $this->current) {
5087
                        $this->index = $index;
5088
                        break;
5089
                    }
5090
                }
5091
                if ($debug) {
5092
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
5093
                }
5094
            } else {
5095
                if ($debug) {
5096
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
5097
                }
5098
            }
5099
        }
5100
    }
5101
5102
    /**
5103
     * Sets the encoding.
5104
     *
5105
     * @param string $enc New encoding
5106
     *
5107
     * @return bool
5108
     *
5109
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5110
     */
5111
    public function set_encoding($enc = 'UTF-8')
5112
    {
5113
        $enc = api_refine_encoding_id($enc);
5114
        if (empty($enc)) {
5115
            $enc = api_get_system_encoding();
5116
        }
5117
        if (api_is_encoding_supported($enc)) {
5118
            $lp = $this->get_id();
5119
            if ($lp != 0) {
5120
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5121
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5122
                        WHERE iid = ".$lp;
5123
                $res = Database::query($sql);
5124
5125
                return $res;
5126
            }
5127
        }
5128
5129
        return false;
5130
    }
5131
5132
    /**
5133
     * Sets the JS lib setting in the database directly.
5134
     * This is the JavaScript library file this lp needs to load on startup.
5135
     *
5136
     * @param string $lib Proximity setting
5137
     *
5138
     * @return bool True on update success. False otherwise.
5139
     */
5140
    public function set_jslib($lib = '')
5141
    {
5142
        $lp = $this->get_id();
5143
5144
        if ($lp != 0) {
5145
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5146
            $lib = Database::escape_string($lib);
5147
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5148
                    WHERE iid = $lp";
5149
            $res = Database::query($sql);
5150
5151
            return $res;
5152
        }
5153
5154
        return false;
5155
    }
5156
5157
    /**
5158
     * Sets the name of the LP maker (publisher) (and save).
5159
     *
5160
     * @param string $name Optional string giving the new content_maker of this learnpath
5161
     *
5162
     * @return bool True
5163
     */
5164
    public function set_maker($name = '')
5165
    {
5166
        if (empty($name)) {
5167
            return false;
5168
        }
5169
        $this->maker = $name;
5170
        $table = Database::get_course_table(TABLE_LP_MAIN);
5171
        $lp_id = $this->get_id();
5172
        $sql = "UPDATE $table SET
5173
                content_maker = '".Database::escape_string($this->maker)."'
5174
                WHERE iid = $lp_id";
5175
        Database::query($sql);
5176
5177
        return true;
5178
    }
5179
5180
    /**
5181
     * Sets the name of the current learnpath (and save).
5182
     *
5183
     * @param string $name Optional string giving the new name of this learnpath
5184
     *
5185
     * @return bool True/False
5186
     */
5187
    public function set_name($name = null)
5188
    {
5189
        if (empty($name)) {
5190
            return false;
5191
        }
5192
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5193
        $name = Database::escape_string($name);
5194
5195
        $this->name = $name;
5196
5197
        $lp_id = $this->get_id();
5198
        $course_id = $this->course_info['real_id'];
5199
        $sql = "UPDATE $lp_table SET
5200
                name = '$name'
5201
                WHERE iid = $lp_id";
5202
        $result = Database::query($sql);
5203
        // If the lp is visible on the homepage, change his name there.
5204
        if (Database::affected_rows($result)) {
5205
            $session_id = api_get_session_id();
5206
            $session_condition = api_get_session_condition($session_id);
5207
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5208
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5209
            $sql = "UPDATE $tbl_tool SET name = '$name'
5210
            	    WHERE
5211
            	        c_id = $course_id AND
5212
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5213
            Database::query($sql);
5214
5215
            return true;
5216
        }
5217
5218
        return false;
5219
    }
5220
5221
    /**
5222
     * Set index specified prefix terms for all items in this path.
5223
     *
5224
     * @param string $terms_string Comma-separated list of terms
5225
     * @param string $prefix       Xapian term prefix
5226
     *
5227
     * @return bool False on error, true otherwise
5228
     */
5229
    public function set_terms_by_prefix($terms_string, $prefix)
5230
    {
5231
        $course_id = api_get_course_int_id();
5232
        if (api_get_setting('search_enabled') !== 'true') {
5233
            return false;
5234
        }
5235
5236
        if (!extension_loaded('xapian')) {
5237
            return false;
5238
        }
5239
5240
        $terms_string = trim($terms_string);
5241
        $terms = explode(',', $terms_string);
5242
        array_walk($terms, 'trim_value');
5243
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5244
5245
        // Don't do anything if no change, verify only at DB, not the search engine.
5246
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5247
            return false;
5248
        }
5249
5250
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5251
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5252
5253
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5254
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5255
        $lp_id = (int) $_POST['lp_id'];
5256
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5257
        $result = Database::query($sql);
5258
        $di = new ChamiloIndexer();
5259
5260
        while ($lp_item = Database::fetch_array($result)) {
5261
            // Get search_did.
5262
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5263
            $sql = 'SELECT * FROM %s
5264
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5265
                    LIMIT 1';
5266
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5267
5268
            //echo $sql; echo '<br>';
5269
            $res = Database::query($sql);
5270
            if (Database::num_rows($res) > 0) {
5271
                $se_ref = Database::fetch_array($res);
5272
                // Compare terms.
5273
                $doc = $di->get_document($se_ref['search_did']);
5274
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5275
                $xterms = [];
5276
                foreach ($xapian_terms as $xapian_term) {
5277
                    $xterms[] = substr($xapian_term['name'], 1);
5278
                }
5279
5280
                $dterms = $terms;
5281
                $missing_terms = array_diff($dterms, $xterms);
5282
                $deprecated_terms = array_diff($xterms, $dterms);
5283
5284
                // Save it to search engine.
5285
                foreach ($missing_terms as $term) {
5286
                    $doc->add_term($prefix.$term, 1);
5287
                }
5288
                foreach ($deprecated_terms as $term) {
5289
                    $doc->remove_term($prefix.$term);
5290
                }
5291
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5292
                $di->getDb()->flush();
5293
            }
5294
        }
5295
5296
        return true;
5297
    }
5298
5299
    /**
5300
     * Sets the theme of the LP (local/remote) (and save).
5301
     *
5302
     * @param string $name Optional string giving the new theme of this learnpath
5303
     *
5304
     * @return bool Returns true if theme name is not empty
5305
     */
5306
    public function set_theme($name = '')
5307
    {
5308
        $this->theme = $name;
5309
        $table = Database::get_course_table(TABLE_LP_MAIN);
5310
        $lp_id = $this->get_id();
5311
        $sql = "UPDATE $table
5312
                SET theme = '".Database::escape_string($this->theme)."'
5313
                WHERE iid = $lp_id";
5314
        Database::query($sql);
5315
5316
        return true;
5317
    }
5318
5319
    /**
5320
     * Sets the image of an LP (and save).
5321
     *
5322
     * @param string $name Optional string giving the new image of this learnpath
5323
     *
5324
     * @return bool Returns true if theme name is not empty
5325
     */
5326
    public function set_preview_image($name = '')
5327
    {
5328
        $this->preview_image = $name;
5329
        $table = Database::get_course_table(TABLE_LP_MAIN);
5330
        $lp_id = $this->get_id();
5331
        $sql = "UPDATE $table SET
5332
                preview_image = '".Database::escape_string($this->preview_image)."'
5333
                WHERE iid = $lp_id";
5334
        Database::query($sql);
5335
5336
        return true;
5337
    }
5338
5339
    /**
5340
     * Sets the author of a LP (and save).
5341
     *
5342
     * @param string $name Optional string giving the new author of this learnpath
5343
     *
5344
     * @return bool Returns true if author's name is not empty
5345
     */
5346
    public function set_author($name = '')
5347
    {
5348
        $this->author = $name;
5349
        $table = Database::get_course_table(TABLE_LP_MAIN);
5350
        $lp_id = $this->get_id();
5351
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5352
                WHERE iid = $lp_id";
5353
        Database::query($sql);
5354
5355
        return true;
5356
    }
5357
5358
    /**
5359
     * Sets the hide_toc_frame parameter of a LP (and save).
5360
     *
5361
     * @param int $hide 1 if frame is hidden 0 then else
5362
     *
5363
     * @return bool Returns true if author's name is not empty
5364
     */
5365
    public function set_hide_toc_frame($hide)
5366
    {
5367
        if (intval($hide) == $hide) {
5368
            $this->hide_toc_frame = $hide;
5369
            $table = Database::get_course_table(TABLE_LP_MAIN);
5370
            $lp_id = $this->get_id();
5371
            $sql = "UPDATE $table SET
5372
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5373
                    WHERE iid = $lp_id";
5374
            Database::query($sql);
5375
5376
            return true;
5377
        }
5378
5379
        return false;
5380
    }
5381
5382
    /**
5383
     * Sets the prerequisite of a LP (and save).
5384
     *
5385
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5386
     *
5387
     * @return bool returns true if prerequisite is not empty
5388
     */
5389
    public function set_prerequisite($prerequisite)
5390
    {
5391
        $this->prerequisite = (int) $prerequisite;
5392
        $table = Database::get_course_table(TABLE_LP_MAIN);
5393
        $lp_id = $this->get_id();
5394
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5395
                WHERE iid = $lp_id";
5396
        Database::query($sql);
5397
5398
        return true;
5399
    }
5400
5401
    /**
5402
     * Sets the location/proximity of the LP (local/remote) (and save).
5403
     *
5404
     * @param string $name Optional string giving the new location of this learnpath
5405
     *
5406
     * @return bool True on success / False on error
5407
     */
5408
    public function set_proximity($name = '')
5409
    {
5410
        if (empty($name)) {
5411
            return false;
5412
        }
5413
5414
        $this->proximity = $name;
5415
        $table = Database::get_course_table(TABLE_LP_MAIN);
5416
        $lp_id = $this->get_id();
5417
        $sql = "UPDATE $table SET
5418
                    content_local = '".Database::escape_string($name)."'
5419
                WHERE iid = $lp_id";
5420
        Database::query($sql);
5421
5422
        return true;
5423
    }
5424
5425
    /**
5426
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5427
     *
5428
     * @param int $id DB ID of the item
5429
     */
5430
    public function set_previous_item($id)
5431
    {
5432
        if ($this->debug > 0) {
5433
            error_log('In learnpath::set_previous_item()', 0);
5434
        }
5435
        $this->last = $id;
5436
    }
5437
5438
    /**
5439
     * Sets use_max_score.
5440
     *
5441
     * @param int $use_max_score Optional string giving the new location of this learnpath
5442
     *
5443
     * @return bool True on success / False on error
5444
     */
5445
    public function set_use_max_score($use_max_score = 1)
5446
    {
5447
        $use_max_score = (int) $use_max_score;
5448
        $this->use_max_score = $use_max_score;
5449
        $table = Database::get_course_table(TABLE_LP_MAIN);
5450
        $lp_id = $this->get_id();
5451
        $sql = "UPDATE $table SET
5452
                    use_max_score = '".$this->use_max_score."'
5453
                WHERE iid = $lp_id";
5454
        Database::query($sql);
5455
5456
        return true;
5457
    }
5458
5459
    /**
5460
     * Sets and saves the expired_on date.
5461
     *
5462
     * @param string $expired_on Optional string giving the new author of this learnpath
5463
     *
5464
     * @throws \Doctrine\ORM\OptimisticLockException
5465
     *
5466
     * @return bool Returns true if author's name is not empty
5467
     */
5468
    public function set_expired_on($expired_on)
5469
    {
5470
        $em = Database::getManager();
5471
        /** @var CLp $lp */
5472
        $lp = $em
5473
            ->getRepository('ChamiloCourseBundle:CLp')
5474
            ->findOneBy(
5475
                [
5476
                    'iid' => $this->get_id(),
5477
                ]
5478
            );
5479
5480
        if (!$lp) {
5481
            return false;
5482
        }
5483
5484
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5485
5486
        $lp->setExpiredOn($this->expired_on);
5487
        $em->persist($lp);
5488
        $em->flush();
5489
5490
        return true;
5491
    }
5492
5493
    /**
5494
     * Sets and saves the publicated_on date.
5495
     *
5496
     * @param string $publicated_on Optional string giving the new author of this learnpath
5497
     *
5498
     * @throws \Doctrine\ORM\OptimisticLockException
5499
     *
5500
     * @return bool Returns true if author's name is not empty
5501
     */
5502
    public function set_publicated_on($publicated_on)
5503
    {
5504
        $em = Database::getManager();
5505
        /** @var CLp $lp */
5506
        $lp = $em
5507
            ->getRepository('ChamiloCourseBundle:CLp')
5508
            ->findOneBy(
5509
                [
5510
                    'iid' => $this->get_id(),
5511
                ]
5512
            );
5513
5514
        if (!$lp) {
5515
            return false;
5516
        }
5517
5518
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5519
        $lp->setPublicatedOn($this->publicated_on);
5520
        $em->persist($lp);
5521
        $em->flush();
5522
5523
        return true;
5524
    }
5525
5526
    /**
5527
     * Sets and saves the expired_on date.
5528
     *
5529
     * @return bool Returns true if author's name is not empty
5530
     */
5531
    public function set_modified_on()
5532
    {
5533
        $this->modified_on = api_get_utc_datetime();
5534
        $table = Database::get_course_table(TABLE_LP_MAIN);
5535
        $lp_id = $this->get_id();
5536
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5537
                WHERE iid = $lp_id";
5538
        Database::query($sql);
5539
5540
        return true;
5541
    }
5542
5543
    /**
5544
     * Sets the object's error message.
5545
     *
5546
     * @param string $error Error message. If empty, reinits the error string
5547
     */
5548
    public function set_error_msg($error = '')
5549
    {
5550
        if ($this->debug > 0) {
5551
            error_log('In learnpath::set_error_msg()', 0);
5552
        }
5553
        if (empty($error)) {
5554
            $this->error = '';
5555
        } else {
5556
            $this->error .= $error;
5557
        }
5558
    }
5559
5560
    /**
5561
     * Launches the current item if not 'sco'
5562
     * (starts timer and make sure there is a record ready in the DB).
5563
     *
5564
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5565
     *
5566
     * @return bool
5567
     */
5568
    public function start_current_item($allow_new_attempt = false)
5569
    {
5570
        $debug = $this->debug;
5571
        if ($debug) {
5572
            error_log('In learnpath::start_current_item()');
5573
            error_log('current: '.$this->current);
5574
        }
5575
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5576
            $type = $this->get_type();
5577
            $item_type = $this->items[$this->current]->get_type();
5578
            if (($type == 2 && $item_type != 'sco') ||
5579
                ($type == 3 && $item_type != 'au') ||
5580
                (
5581
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5582
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5583
                )
5584
            ) {
5585
                if ($debug) {
5586
                    error_log('item type: '.$item_type);
5587
                    error_log('lp type: '.$type);
5588
                }
5589
                $this->items[$this->current]->open($allow_new_attempt);
5590
                $this->autocomplete_parents($this->current);
5591
                $prereq_check = $this->prerequisites_match($this->current);
5592
                if ($debug) {
5593
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5594
                }
5595
                $this->items[$this->current]->save(false, $prereq_check);
5596
            }
5597
            // If sco, then it is supposed to have been updated by some other call.
5598
            if ($item_type == 'sco') {
5599
                $this->items[$this->current]->restart();
5600
            }
5601
        }
5602
        if ($debug) {
5603
            error_log('lp_view_session_id: '.$this->lp_view_session_id);
5604
            error_log('api_get_session_id: '.api_get_session_id());
5605
            error_log('End of learnpath::start_current_item()');
5606
        }
5607
5608
        return true;
5609
    }
5610
5611
    /**
5612
     * Stops the processing and counters for the old item (as held in $this->last).
5613
     *
5614
     * @return bool True/False
5615
     */
5616
    public function stop_previous_item()
5617
    {
5618
        $debug = $this->debug;
5619
        if ($debug) {
5620
            error_log('In learnpath::stop_previous_item()');
5621
        }
5622
5623
        if ($this->last != 0 && $this->last != $this->current &&
5624
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5625
        ) {
5626
            if ($debug) {
5627
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5628
            }
5629
            switch ($this->get_type()) {
5630
                case '3':
5631
                    if ($this->items[$this->last]->get_type() != 'au') {
5632
                        if ($debug) {
5633
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5634
                        }
5635
                        $this->items[$this->last]->close();
5636
                    } else {
5637
                        if ($debug) {
5638
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5639
                        }
5640
                    }
5641
                    break;
5642
                case '2':
5643
                    if ($this->items[$this->last]->get_type() != 'sco') {
5644
                        if ($debug) {
5645
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5646
                        }
5647
                        $this->items[$this->last]->close();
5648
                    } else {
5649
                        if ($debug) {
5650
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5651
                        }
5652
                    }
5653
                    break;
5654
                case '1':
5655
                default:
5656
                    if ($debug) {
5657
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5658
                    }
5659
                    $this->items[$this->last]->close();
5660
                    break;
5661
            }
5662
        } else {
5663
            if ($debug) {
5664
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5665
            }
5666
5667
            return false;
5668
        }
5669
5670
        return true;
5671
    }
5672
5673
    /**
5674
     * Updates the default view mode from fullscreen to embedded and inversely.
5675
     *
5676
     * @return string The current default view mode ('fullscreen' or 'embedded')
5677
     */
5678
    public function update_default_view_mode()
5679
    {
5680
        $table = Database::get_course_table(TABLE_LP_MAIN);
5681
        $sql = "SELECT * FROM $table
5682
                WHERE iid = ".$this->get_id();
5683
        $res = Database::query($sql);
5684
        if (Database::num_rows($res) > 0) {
5685
            $row = Database::fetch_array($res);
5686
            $default_view_mode = $row['default_view_mod'];
5687
            $view_mode = $default_view_mode;
5688
            switch ($default_view_mode) {
5689
                case 'fullscreen': // default with popup
5690
                    $view_mode = 'embedded';
5691
                    break;
5692
                case 'embedded': // default view with left menu
5693
                    $view_mode = 'embedframe';
5694
                    break;
5695
                case 'embedframe': //folded menu
5696
                    $view_mode = 'impress';
5697
                    break;
5698
                case 'impress':
5699
                    $view_mode = 'fullscreen';
5700
                    break;
5701
            }
5702
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5703
                    WHERE iid = ".$this->get_id();
5704
            Database::query($sql);
5705
            $this->mode = $view_mode;
5706
5707
            return $view_mode;
5708
        }
5709
5710
        return -1;
5711
    }
5712
5713
    /**
5714
     * Updates the default behaviour about auto-commiting SCORM updates.
5715
     *
5716
     * @return bool True if auto-commit has been set to 'on', false otherwise
5717
     */
5718
    public function update_default_scorm_commit()
5719
    {
5720
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5721
        $sql = "SELECT * FROM $lp_table
5722
                WHERE iid = ".$this->get_id();
5723
        $res = Database::query($sql);
5724
        if (Database::num_rows($res) > 0) {
5725
            $row = Database::fetch_array($res);
5726
            $force = $row['force_commit'];
5727
            if ($force == 1) {
5728
                $force = 0;
5729
                $force_return = false;
5730
            } elseif ($force == 0) {
5731
                $force = 1;
5732
                $force_return = true;
5733
            }
5734
            $sql = "UPDATE $lp_table SET force_commit = $force
5735
                    WHERE iid = ".$this->get_id();
5736
            Database::query($sql);
5737
            $this->force_commit = $force_return;
5738
5739
            return $force_return;
5740
        }
5741
5742
        return -1;
5743
    }
5744
5745
    /**
5746
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5747
     *
5748
     * @return bool True on success, false on failure
5749
     */
5750
    public function update_display_order()
5751
    {
5752
        $course_id = api_get_course_int_id();
5753
        $table = Database::get_course_table(TABLE_LP_MAIN);
5754
        $sql = "SELECT * FROM $table
5755
                WHERE c_id = $course_id
5756
                ORDER BY display_order";
5757
        $res = Database::query($sql);
5758
        if ($res === false) {
5759
            return false;
5760
        }
5761
5762
        $num = Database::num_rows($res);
5763
        // First check the order is correct, globally (might be wrong because
5764
        // of versions < 1.8.4).
5765
        if ($num > 0) {
5766
            $i = 1;
5767
            while ($row = Database::fetch_array($res)) {
5768
                if ($row['display_order'] != $i) {
5769
                    // If we find a gap in the order, we need to fix it.
5770
                    $sql = "UPDATE $table SET display_order = $i
5771
                            WHERE iid = ".$row['iid'];
5772
                    Database::query($sql);
5773
                }
5774
                $i++;
5775
            }
5776
        }
5777
5778
        return true;
5779
    }
5780
5781
    /**
5782
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5783
     *
5784
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5785
     */
5786
    public function update_reinit()
5787
    {
5788
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5789
        $sql = "SELECT * FROM $lp_table
5790
                WHERE iid = ".$this->get_id();
5791
        $res = Database::query($sql);
5792
        if (Database::num_rows($res) > 0) {
5793
            $row = Database::fetch_array($res);
5794
            $force = $row['prevent_reinit'];
5795
            if ($force == 1) {
5796
                $force = 0;
5797
            } elseif ($force == 0) {
5798
                $force = 1;
5799
            }
5800
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5801
                    WHERE iid = ".$this->get_id();
5802
            Database::query($sql);
5803
            $this->prevent_reinit = $force;
5804
5805
            return $force;
5806
        }
5807
5808
        return -1;
5809
    }
5810
5811
    /**
5812
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5813
     *
5814
     * @return string 'single', 'multi' or 'seriousgame'
5815
     *
5816
     * @author ndiechburg <[email protected]>
5817
     */
5818
    public function get_attempt_mode()
5819
    {
5820
        //Set default value for seriousgame_mode
5821
        if (!isset($this->seriousgame_mode)) {
5822
            $this->seriousgame_mode = 0;
5823
        }
5824
        // Set default value for prevent_reinit
5825
        if (!isset($this->prevent_reinit)) {
5826
            $this->prevent_reinit = 1;
5827
        }
5828
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5829
            return 'seriousgame';
5830
        }
5831
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5832
            return 'single';
5833
        }
5834
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5835
            return 'multiple';
5836
        }
5837
5838
        return 'single';
5839
    }
5840
5841
    /**
5842
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5843
     *
5844
     * @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...
5845
     *
5846
     * @return bool
5847
     *
5848
     * @author ndiechburg <[email protected]>
5849
     */
5850
    public function set_attempt_mode($mode)
5851
    {
5852
        switch ($mode) {
5853
            case 'seriousgame':
5854
                $sg_mode = 1;
5855
                $prevent_reinit = 1;
5856
                break;
5857
            case 'single':
5858
                $sg_mode = 0;
5859
                $prevent_reinit = 1;
5860
                break;
5861
            case 'multiple':
5862
                $sg_mode = 0;
5863
                $prevent_reinit = 0;
5864
                break;
5865
            default:
5866
                $sg_mode = 0;
5867
                $prevent_reinit = 0;
5868
                break;
5869
        }
5870
        $this->prevent_reinit = $prevent_reinit;
5871
        $this->seriousgame_mode = $sg_mode;
5872
        $table = Database::get_course_table(TABLE_LP_MAIN);
5873
        $sql = "UPDATE $table SET
5874
                prevent_reinit = $prevent_reinit ,
5875
                seriousgame_mode = $sg_mode
5876
                WHERE iid = ".$this->get_id();
5877
        $res = Database::query($sql);
5878
        if ($res) {
5879
            return true;
5880
        } else {
5881
            return false;
5882
        }
5883
    }
5884
5885
    /**
5886
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5887
     *
5888
     * @author ndiechburg <[email protected]>
5889
     */
5890
    public function switch_attempt_mode()
5891
    {
5892
        $mode = $this->get_attempt_mode();
5893
        switch ($mode) {
5894
            case 'single':
5895
                $next_mode = 'multiple';
5896
                break;
5897
            case 'multiple':
5898
                $next_mode = 'seriousgame';
5899
                break;
5900
            case 'seriousgame':
5901
            default:
5902
                $next_mode = 'single';
5903
                break;
5904
        }
5905
        $this->set_attempt_mode($next_mode);
5906
    }
5907
5908
    /**
5909
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5910
     * but possibility to do again a completed item.
5911
     *
5912
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5913
     *
5914
     * @author ndiechburg <[email protected]>
5915
     */
5916
    public function set_seriousgame_mode()
5917
    {
5918
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5919
        $sql = "SELECT * FROM $lp_table
5920
                WHERE iid = ".$this->get_id();
5921
        $res = Database::query($sql);
5922
        if (Database::num_rows($res) > 0) {
5923
            $row = Database::fetch_array($res);
5924
            $force = $row['seriousgame_mode'];
5925
            if ($force == 1) {
5926
                $force = 0;
5927
            } elseif ($force == 0) {
5928
                $force = 1;
5929
            }
5930
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5931
			        WHERE iid = ".$this->get_id();
5932
            Database::query($sql);
5933
            $this->seriousgame_mode = $force;
5934
5935
            return $force;
5936
        }
5937
5938
        return -1;
5939
    }
5940
5941
    /**
5942
     * Updates the "scorm_debug" value that shows or hide the debug window.
5943
     *
5944
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5945
     */
5946
    public function update_scorm_debug()
5947
    {
5948
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5949
        $sql = "SELECT * FROM $lp_table
5950
                WHERE iid = ".$this->get_id();
5951
        $res = Database::query($sql);
5952
        if (Database::num_rows($res) > 0) {
5953
            $row = Database::fetch_array($res);
5954
            $force = $row['debug'];
5955
            if ($force == 1) {
5956
                $force = 0;
5957
            } elseif ($force == 0) {
5958
                $force = 1;
5959
            }
5960
            $sql = "UPDATE $lp_table SET debug = $force
5961
                    WHERE iid = ".$this->get_id();
5962
            Database::query($sql);
5963
            $this->scorm_debug = $force;
5964
5965
            return $force;
5966
        }
5967
5968
        return -1;
5969
    }
5970
5971
    /**
5972
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5973
     *
5974
     * @author Kevin Van Den Haute
5975
     *
5976
     * @param  array
5977
     */
5978
    public function tree_array($array)
5979
    {
5980
        $array = $this->sort_tree_array($array);
5981
        $this->create_tree_array($array);
5982
    }
5983
5984
    /**
5985
     * Creates an array with the elements of the learning path tree in it.
5986
     *
5987
     * @author Kevin Van Den Haute
5988
     *
5989
     * @param array $array
5990
     * @param int   $parent
5991
     * @param int   $depth
5992
     * @param array $tmp
5993
     */
5994
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5995
    {
5996
        if (is_array($array)) {
5997
            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...
5998
                if ($array[$i]['parent_item_id'] == $parent) {
5999
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
6000
                        $tmp[] = $array[$i]['parent_item_id'];
6001
                        $depth++;
6002
                    }
6003
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
6004
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
6005
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
6006
6007
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
6008
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
6009
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
6010
                    $this->arrMenu[] = [
6011
                        'id' => $array[$i]['id'],
6012
                        'ref' => $ref,
6013
                        'item_type' => $array[$i]['item_type'],
6014
                        'title' => $array[$i]['title'],
6015
                        'title_raw' => $array[$i]['title_raw'],
6016
                        'path' => $path,
6017
                        'description' => $array[$i]['description'],
6018
                        'parent_item_id' => $array[$i]['parent_item_id'],
6019
                        'previous_item_id' => $array[$i]['previous_item_id'],
6020
                        'next_item_id' => $array[$i]['next_item_id'],
6021
                        'min_score' => $array[$i]['min_score'],
6022
                        'max_score' => $array[$i]['max_score'],
6023
                        'mastery_score' => $array[$i]['mastery_score'],
6024
                        'display_order' => $array[$i]['display_order'],
6025
                        'prerequisite' => $preq,
6026
                        'depth' => $depth,
6027
                        'audio' => $audio,
6028
                        'prerequisite_min_score' => $prerequisiteMinScore,
6029
                        'prerequisite_max_score' => $prerequisiteMaxScore,
6030
                    ];
6031
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
6032
                }
6033
            }
6034
        }
6035
    }
6036
6037
    /**
6038
     * Sorts a multi dimensional array by parent id and display order.
6039
     *
6040
     * @author Kevin Van Den Haute
6041
     *
6042
     * @param array $array (array with al the learning path items in it)
6043
     *
6044
     * @return array
6045
     */
6046
    public function sort_tree_array($array)
6047
    {
6048
        foreach ($array as $key => $row) {
6049
            $parent[$key] = $row['parent_item_id'];
6050
            $position[$key] = $row['display_order'];
6051
        }
6052
6053
        if (count($array) > 0) {
6054
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
6055
        }
6056
6057
        return $array;
6058
    }
6059
6060
    /**
6061
     * Function that creates a html list of learning path items so that we can add audio files to them.
6062
     *
6063
     * @author Kevin Van Den Haute
6064
     *
6065
     * @return string
6066
     */
6067
    public function overview()
6068
    {
6069
        $return = '';
6070
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
6071
6072
        // we need to start a form when we want to update all the mp3 files
6073
        if ($update_audio == 'true') {
6074
            $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">';
6075
        }
6076
        $return .= '<div id="message"></div>';
6077
        if (count($this->items) == 0) {
6078
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
6079
        } else {
6080
            $return_audio = '<table class="table table-hover table-striped data_table">';
6081
            $return_audio .= '<tr>';
6082
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
6083
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
6084
            $return_audio .= '</tr>';
6085
6086
            if ($update_audio != 'true') {
6087
                $return .= '<div class="col-md-12">';
6088
                $return .= self::return_new_tree($update_audio);
6089
                $return .= '</div>';
6090
                $return .= Display::div(
6091
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
6092
                    ['style' => 'float:left; margin-top:15px;width:100%']
6093
                );
6094
            } else {
6095
                $return_audio .= self::return_new_tree($update_audio);
6096
                $return .= $return_audio.'</table>';
6097
            }
6098
6099
            // We need to close the form when we are updating the mp3 files.
6100
            if ($update_audio == 'true') {
6101
                $return .= '<div class="footer-audio">';
6102
                $return .= Display::button(
6103
                    'save_audio',
6104
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
6105
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6106
                );
6107
                $return .= '</div>';
6108
            }
6109
        }
6110
6111
        // We need to close the form when we are updating the mp3 files.
6112
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6113
            $return .= '</form>';
6114
        }
6115
6116
        return $return;
6117
    }
6118
6119
    /**
6120
     * @param string $update_audio
6121
     *
6122
     * @return array
6123
     */
6124
    public function processBuildMenuElements($update_audio = 'false')
6125
    {
6126
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6127
        $arrLP = $this->getItemsForForm();
6128
6129
        $this->tree_array($arrLP);
6130
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6131
        unset($this->arrMenu);
6132
        $default_data = null;
6133
        $default_content = null;
6134
        $elements = [];
6135
        $return_audio = null;
6136
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6137
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6138
        $countItems = count($arrLP);
6139
6140
        $upIcon = Display::return_icon(
6141
            'up.png',
6142
            get_lang('Up'),
6143
            [],
6144
            ICON_SIZE_TINY
6145
        );
6146
6147
        $disableUpIcon = Display::return_icon(
6148
            'up_na.png',
6149
            get_lang('Up'),
6150
            [],
6151
            ICON_SIZE_TINY
6152
        );
6153
6154
        $downIcon = Display::return_icon(
6155
            'down.png',
6156
            get_lang('Down'),
6157
            [],
6158
            ICON_SIZE_TINY
6159
        );
6160
6161
        $disableDownIcon = Display::return_icon(
6162
            'down_na.png',
6163
            get_lang('Down'),
6164
            [],
6165
            ICON_SIZE_TINY
6166
        );
6167
6168
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6169
6170
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6171
        $plugin = null;
6172
        if ($pluginCalendar) {
6173
            $plugin = LearningCalendarPlugin::create();
6174
        }
6175
6176
        for ($i = 0; $i < $countItems; $i++) {
6177
            $parent_id = $arrLP[$i]['parent_item_id'];
6178
            $title = $arrLP[$i]['title'];
6179
            $title_cut = $arrLP[$i]['title_raw'];
6180
            if ($show === false) {
6181
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6182
            }
6183
            // Link for the documents
6184
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6185
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6186
                $title_cut = Display::url(
6187
                    $title_cut,
6188
                    $url,
6189
                    [
6190
                        'class' => 'ajax moved',
6191
                        'data-title' => $title,
6192
                        'title' => $title,
6193
                    ]
6194
                );
6195
            }
6196
6197
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6198
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6199
                Session::write('pathItem', $arrLP[$i]['path']);
6200
            }
6201
6202
            $oddClass = 'row_even';
6203
            if (($i % 2) == 0) {
6204
                $oddClass = 'row_odd';
6205
            }
6206
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6207
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6208
6209
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6210
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6211
            } else {
6212
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6213
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6214
                } else {
6215
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6216
                        $icon = Display::return_icon('certificate.png');
6217
                    } else {
6218
                        $icon = Display::return_icon('folder_document.gif');
6219
                    }
6220
                }
6221
            }
6222
6223
            // The audio column.
6224
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6225
            $audio = '';
6226
            if (!$update_audio || $update_audio != 'true') {
6227
                if (empty($arrLP[$i]['audio'])) {
6228
                    $audio .= '';
6229
                }
6230
            } else {
6231
                $types = self::getChapterTypes();
6232
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6233
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6234
                    if (!empty($arrLP[$i]['audio'])) {
6235
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6236
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6237
                    }
6238
                }
6239
            }
6240
6241
            $return_audio .= Display::span($icon.' '.$title).
6242
                Display::tag(
6243
                    'td',
6244
                    $audio,
6245
                    ['style' => '']
6246
                );
6247
            $return_audio .= '</td>';
6248
            $move_icon = '';
6249
            $move_item_icon = '';
6250
            $edit_icon = '';
6251
            $delete_icon = '';
6252
            $audio_icon = '';
6253
            $prerequisities_icon = '';
6254
            $forumIcon = '';
6255
            $previewIcon = '';
6256
            $pluginCalendarIcon = '';
6257
            $orderIcons = '';
6258
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6259
6260
            if ($is_allowed_to_edit) {
6261
                if (!$update_audio || $update_audio != 'true') {
6262
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6263
                        $move_icon .= '<a class="moved" href="#">';
6264
                        $move_icon .= Display::return_icon(
6265
                            'move_everywhere.png',
6266
                            get_lang('Move'),
6267
                            [],
6268
                            ICON_SIZE_TINY
6269
                        );
6270
                        $move_icon .= '</a>';
6271
                    }
6272
                }
6273
6274
                // No edit for this item types
6275
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6276
                    if ($arrLP[$i]['item_type'] != 'dir') {
6277
                        $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">';
6278
                        $edit_icon .= Display::return_icon(
6279
                            'edit.png',
6280
                            get_lang('LearnpathEditModule'),
6281
                            [],
6282
                            ICON_SIZE_TINY
6283
                        );
6284
                        $edit_icon .= '</a>';
6285
6286
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6287
                            $forumThread = null;
6288
                            if (isset($this->items[$arrLP[$i]['id']])) {
6289
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6290
                                    $this->course_int_id,
6291
                                    $this->lp_session_id
6292
                                );
6293
                            }
6294
                            if ($forumThread) {
6295
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6296
                                        'action' => 'dissociate_forum',
6297
                                        'id' => $arrLP[$i]['id'],
6298
                                        'lp_id' => $this->lp_id,
6299
                                    ]);
6300
                                $forumIcon = Display::url(
6301
                                    Display::return_icon(
6302
                                        'forum.png',
6303
                                        get_lang('DissociateForumToLPItem'),
6304
                                        [],
6305
                                        ICON_SIZE_TINY
6306
                                    ),
6307
                                    $forumIconUrl,
6308
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6309
                                );
6310
                            } else {
6311
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6312
                                        'action' => 'create_forum',
6313
                                        'id' => $arrLP[$i]['id'],
6314
                                        'lp_id' => $this->lp_id,
6315
                                    ]);
6316
                                $forumIcon = Display::url(
6317
                                    Display::return_icon(
6318
                                        'forum.png',
6319
                                        get_lang('AssociateForumToLPItem'),
6320
                                        [],
6321
                                        ICON_SIZE_TINY
6322
                                    ),
6323
                                    $forumIconUrl,
6324
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6325
                                );
6326
                            }
6327
                        }
6328
                    } else {
6329
                        $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">';
6330
                        $edit_icon .= Display::return_icon(
6331
                            'edit.png',
6332
                            get_lang('LearnpathEditModule'),
6333
                            [],
6334
                            ICON_SIZE_TINY
6335
                        );
6336
                        $edit_icon .= '</a>';
6337
                    }
6338
                } else {
6339
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6340
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6341
                        $edit_icon .= Display::return_icon(
6342
                            'edit.png',
6343
                            get_lang('Edit'),
6344
                            [],
6345
                            ICON_SIZE_TINY
6346
                        );
6347
                        $edit_icon .= '</a>';
6348
                    }
6349
                }
6350
6351
                if ($pluginCalendar) {
6352
                    $pluginLink = $pluginUrl.
6353
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6354
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6355
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6356
                    if ($itemInfo && $itemInfo['value'] == 1) {
6357
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6358
                    }
6359
                    $pluginCalendarIcon = Display::url(
6360
                        $iconCalendar,
6361
                        $pluginLink,
6362
                        ['class' => 'btn btn-default']
6363
                    );
6364
                }
6365
6366
                if ($arrLP[$i]['item_type'] != 'final_item') {
6367
                    $orderIcons = Display::url(
6368
                        $upIcon,
6369
                        'javascript:void(0)',
6370
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6371
                    );
6372
                    $orderIcons .= Display::url(
6373
                        $downIcon,
6374
                        'javascript:void(0)',
6375
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6376
                    );
6377
                }
6378
6379
                $delete_icon .= ' <a
6380
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6381
                    onclick="return confirmation(\''.addslashes($title).'\');"
6382
                    class="btn btn-default">';
6383
                $delete_icon .= Display::return_icon(
6384
                    'delete.png',
6385
                    get_lang('LearnpathDeleteModule'),
6386
                    [],
6387
                    ICON_SIZE_TINY
6388
                );
6389
                $delete_icon .= '</a>';
6390
6391
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6392
                $previewImage = Display::return_icon(
6393
                    'preview_view.png',
6394
                    get_lang('Preview'),
6395
                    [],
6396
                    ICON_SIZE_TINY
6397
                );
6398
6399
                switch ($arrLP[$i]['item_type']) {
6400
                    case TOOL_DOCUMENT:
6401
                    case TOOL_LP_FINAL_ITEM:
6402
                    case TOOL_READOUT_TEXT:
6403
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6404
                        $previewIcon = Display::url(
6405
                            $previewImage,
6406
                            $urlPreviewLink,
6407
                            [
6408
                                'target' => '_blank',
6409
                                'class' => 'btn btn-default',
6410
                                'data-title' => $arrLP[$i]['title'],
6411
                                'title' => $arrLP[$i]['title'],
6412
                            ]
6413
                        );
6414
                        break;
6415
                    case TOOL_THREAD:
6416
                    case TOOL_FORUM:
6417
                    case TOOL_QUIZ:
6418
                    case TOOL_STUDENTPUBLICATION:
6419
                    case TOOL_LP_FINAL_ITEM:
6420
                    case TOOL_LINK:
6421
                        $class = 'btn btn-default';
6422
                        $target = '_blank';
6423
                        $link = self::rl_get_resource_link_for_learnpath(
6424
                            $this->course_int_id,
6425
                            $this->lp_id,
6426
                            $arrLP[$i]['id'],
6427
                            0
6428
                        );
6429
                        $previewIcon = Display::url(
6430
                            $previewImage,
6431
                            $link,
6432
                            [
6433
                                'class' => $class,
6434
                                'data-title' => $arrLP[$i]['title'],
6435
                                'title' => $arrLP[$i]['title'],
6436
                                'target' => $target,
6437
                            ]
6438
                        );
6439
                        break;
6440
                    default:
6441
                        $previewIcon = Display::url(
6442
                            $previewImage,
6443
                            $url.'&action=view_item',
6444
                            ['class' => 'btn btn-default', 'target' => '_blank']
6445
                        );
6446
                        break;
6447
                }
6448
6449
                if ($arrLP[$i]['item_type'] != 'dir') {
6450
                    $prerequisities_icon = Display::url(
6451
                        Display::return_icon(
6452
                            'accept.png',
6453
                            get_lang('LearnpathPrerequisites'),
6454
                            [],
6455
                            ICON_SIZE_TINY
6456
                        ),
6457
                        $url.'&action=edit_item_prereq',
6458
                        ['class' => 'btn btn-default']
6459
                    );
6460
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6461
                        $move_item_icon = Display::url(
6462
                            Display::return_icon(
6463
                                'move.png',
6464
                                get_lang('Move'),
6465
                                [],
6466
                                ICON_SIZE_TINY
6467
                            ),
6468
                            $url.'&action=move_item',
6469
                            ['class' => 'btn btn-default']
6470
                        );
6471
                    }
6472
                    $audio_icon = Display::url(
6473
                        Display::return_icon(
6474
                            'audio.png',
6475
                            get_lang('UplUpload'),
6476
                            [],
6477
                            ICON_SIZE_TINY
6478
                        ),
6479
                        $url.'&action=add_audio',
6480
                        ['class' => 'btn btn-default']
6481
                    );
6482
                }
6483
            }
6484
            if ($update_audio != 'true') {
6485
                $row = $move_icon.' '.$icon.
6486
                    Display::span($title_cut).
6487
                    Display::tag(
6488
                        'div',
6489
                        "<div class=\"btn-group btn-group-xs\">
6490
                                    $previewIcon
6491
                                    $audio
6492
                                    $edit_icon
6493
                                    $pluginCalendarIcon
6494
                                    $forumIcon
6495
                                    $prerequisities_icon
6496
                                    $move_item_icon
6497
                                    $audio_icon
6498
                                    $orderIcons
6499
                                    $delete_icon
6500
                                </div>",
6501
                        ['class' => 'btn-toolbar button_actions']
6502
                    );
6503
            } else {
6504
                $row =
6505
                    Display::span($title.$icon).
6506
                    Display::span($audio, ['class' => 'button_actions']);
6507
            }
6508
6509
            $default_data[$arrLP[$i]['id']] = $row;
6510
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6511
6512
            if (empty($parent_id)) {
6513
                $elements[$arrLP[$i]['id']]['data'] = $row;
6514
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6515
            } else {
6516
                $parent_arrays = [];
6517
                if ($arrLP[$i]['depth'] > 1) {
6518
                    // Getting list of parents
6519
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6520
                        foreach ($arrLP as $item) {
6521
                            if ($item['id'] == $parent_id) {
6522
                                if ($item['parent_item_id'] == 0) {
6523
                                    $parent_id = $item['id'];
6524
                                    break;
6525
                                } else {
6526
                                    $parent_id = $item['parent_item_id'];
6527
                                    if (empty($parent_arrays)) {
6528
                                        $parent_arrays[] = intval($item['id']);
6529
                                    }
6530
                                    $parent_arrays[] = $parent_id;
6531
                                    break;
6532
                                }
6533
                            }
6534
                        }
6535
                    }
6536
                }
6537
6538
                if (!empty($parent_arrays)) {
6539
                    $parent_arrays = array_reverse($parent_arrays);
6540
                    $val = '$elements';
6541
                    $x = 0;
6542
                    foreach ($parent_arrays as $item) {
6543
                        if ($x != count($parent_arrays) - 1) {
6544
                            $val .= '["'.$item.'"]["children"]';
6545
                        } else {
6546
                            $val .= '["'.$item.'"]["children"]';
6547
                        }
6548
                        $x++;
6549
                    }
6550
                    $val .= "";
6551
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6552
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6553
                } else {
6554
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6555
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6556
                }
6557
            }
6558
        }
6559
6560
        return [
6561
            'elements' => $elements,
6562
            'default_data' => $default_data,
6563
            'default_content' => $default_content,
6564
            'return_audio' => $return_audio,
6565
        ];
6566
    }
6567
6568
    /**
6569
     * @param string $updateAudio true/false strings
6570
     *
6571
     * @return string
6572
     */
6573
    public function returnLpItemList($updateAudio)
6574
    {
6575
        $result = $this->processBuildMenuElements($updateAudio);
6576
6577
        $html = self::print_recursive(
6578
            $result['elements'],
6579
            $result['default_data'],
6580
            $result['default_content']
6581
        );
6582
6583
        if (!empty($html)) {
6584
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6585
        }
6586
6587
        return $html;
6588
    }
6589
6590
    /**
6591
     * @param string $update_audio
6592
     * @param bool   $drop_element_here
6593
     *
6594
     * @return string
6595
     */
6596
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6597
    {
6598
        $result = $this->processBuildMenuElements($update_audio);
6599
6600
        $list = '<ul id="lp_item_list">';
6601
        $tree = $this->print_recursive(
6602
            $result['elements'],
6603
            $result['default_data'],
6604
            $result['default_content']
6605
        );
6606
6607
        if (!empty($tree)) {
6608
            $list .= $tree;
6609
        } else {
6610
            if ($drop_element_here) {
6611
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6612
            }
6613
        }
6614
        $list .= '</ul>';
6615
6616
        $return = Display::panelCollapse(
6617
            $this->name,
6618
            $list,
6619
            'scorm-list',
6620
            null,
6621
            'scorm-list-accordion',
6622
            'scorm-list-collapse'
6623
        );
6624
6625
        if ($update_audio === 'true') {
6626
            $return = $result['return_audio'];
6627
        }
6628
6629
        return $return;
6630
    }
6631
6632
    /**
6633
     * @param array $elements
6634
     * @param array $default_data
6635
     * @param array $default_content
6636
     *
6637
     * @return string
6638
     */
6639
    public function print_recursive($elements, $default_data, $default_content)
6640
    {
6641
        $return = '';
6642
        foreach ($elements as $key => $item) {
6643
            if (isset($item['load_data']) || empty($item['data'])) {
6644
                $item['data'] = $default_data[$item['load_data']];
6645
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6646
            }
6647
            $sub_list = '';
6648
            if (isset($item['type']) && $item['type'] === 'dir') {
6649
                // empty value
6650
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6651
            }
6652
            if (empty($item['children'])) {
6653
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6654
                $active = null;
6655
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6656
                    $active = 'active';
6657
                }
6658
                $return .= Display::tag(
6659
                    'li',
6660
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6661
                    ['id' => $key, 'class' => 'record li_container']
6662
                );
6663
            } else {
6664
                // Sections
6665
                $data = '';
6666
                if (isset($item['children'])) {
6667
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6668
                }
6669
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6670
                $return .= Display::tag(
6671
                    'li',
6672
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6673
                    ['id' => $key, 'class' => 'record li_container']
6674
                );
6675
            }
6676
        }
6677
6678
        return $return;
6679
    }
6680
6681
    /**
6682
     * This function builds the action menu.
6683
     *
6684
     * @param bool   $returnString           Optional
6685
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6686
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6687
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6688
     * @param string $action
6689
     * @param array  $extraField
6690
     *
6691
     * @return string
6692
     */
6693
    public function build_action_menu(
6694
        $returnString = false,
6695
        $showRequirementButtons = true,
6696
        $isConfigPage = false,
6697
        $allowExpand = true,
6698
        $action = '',
6699
        $extraField = []
6700
    ) {
6701
        $actionsRight = '';
6702
        $lpId = $this->lp_id;
6703
        if (!isset($extraField['backTo']) && empty($extraField['backTo'])) {
6704
            $back = Display::url(
6705
                Display::return_icon(
6706
                    'back.png',
6707
                    get_lang('ReturnToLearningPaths'),
6708
                    '',
6709
                    ICON_SIZE_MEDIUM
6710
                ),
6711
                'lp_controller.php?'.api_get_cidreq()
6712
            );
6713
        } else {
6714
            $back = Display::url(
6715
                Display::return_icon(
6716
                    'back.png',
6717
                    get_lang('Back'),
6718
                    '',
6719
                    ICON_SIZE_MEDIUM
6720
                ),
6721
                $extraField['backTo']
6722
            );
6723
        }
6724
6725
        $actionsLeft = $back;
6726
        $actionsLeft .= Display::url(
6727
            Display::return_icon(
6728
                'preview_view.png',
6729
                get_lang('Preview'),
6730
                '',
6731
                ICON_SIZE_MEDIUM
6732
            ),
6733
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6734
                'action' => 'view',
6735
                'lp_id' => $lpId,
6736
                'isStudentView' => 'true',
6737
            ])
6738
        );
6739
6740
        $actionsLeft .= Display::url(
6741
            Display::return_icon(
6742
                'upload_audio.png',
6743
                get_lang('UpdateAllAudioFragments'),
6744
                '',
6745
                ICON_SIZE_MEDIUM
6746
            ),
6747
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6748
                'action' => 'admin_view',
6749
                'lp_id' => $lpId,
6750
                'updateaudio' => 'true',
6751
            ])
6752
        );
6753
6754
        $subscriptionSettings = self::getSubscriptionSettings();
6755
        $request = api_request_uri();
6756
        if (strpos($request, 'edit') === false) {
6757
            $actionsLeft .= Display::url(
6758
                Display::return_icon(
6759
                    'settings.png',
6760
                    get_lang('CourseSettings'),
6761
                    '',
6762
                    ICON_SIZE_MEDIUM
6763
                ),
6764
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6765
                    'action' => 'edit',
6766
                    'lp_id' => $lpId,
6767
                ])
6768
            );
6769
        }
6770
6771
        if ((strpos($request, 'build') === false &&
6772
            strpos($request, 'add_item') === false) ||
6773
            in_array($action, ['add_audio'])
6774
        ) {
6775
            $actionsLeft .= Display::url(
6776
                Display::return_icon(
6777
                    'edit.png',
6778
                    get_lang('Edit'),
6779
                    '',
6780
                    ICON_SIZE_MEDIUM
6781
                ),
6782
                'lp_controller.php?'.http_build_query([
6783
                    'action' => 'build',
6784
                    'lp_id' => $lpId,
6785
                ]).'&'.api_get_cidreq()
6786
            );
6787
        }
6788
6789
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6790
            if ($this->subscribeUsers == 1 &&
6791
                $subscriptionSettings['allow_add_users_to_lp']) {
6792
                $actionsLeft .= Display::url(
6793
                    Display::return_icon(
6794
                        'user.png',
6795
                        get_lang('SubscribeUsersToLp'),
6796
                        '',
6797
                        ICON_SIZE_MEDIUM
6798
                    ),
6799
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6800
                );
6801
            }
6802
        }
6803
6804
        if ($allowExpand) {
6805
            $actionsLeft .= Display::url(
6806
                Display::return_icon(
6807
                    'expand.png',
6808
                    get_lang('Expand'),
6809
                    ['id' => 'expand'],
6810
                    ICON_SIZE_MEDIUM
6811
                ).
6812
                Display::return_icon(
6813
                    'contract.png',
6814
                    get_lang('Collapse'),
6815
                    ['id' => 'contract', 'class' => 'hide'],
6816
                    ICON_SIZE_MEDIUM
6817
                ),
6818
                '#',
6819
                ['role' => 'button', 'id' => 'hide_bar_template']
6820
            );
6821
        }
6822
6823
        if ($showRequirementButtons) {
6824
            $buttons = [
6825
                [
6826
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6827
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6828
                        'action' => 'set_previous_step_as_prerequisite',
6829
                        'lp_id' => $lpId,
6830
                    ]),
6831
                ],
6832
                [
6833
                    'title' => get_lang('ClearAllPrerequisites'),
6834
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6835
                        'action' => 'clear_prerequisites',
6836
                        'lp_id' => $lpId,
6837
                    ]),
6838
                ],
6839
            ];
6840
            $actionsRight = Display::groupButtonWithDropDown(
6841
                get_lang('PrerequisitesOptions'),
6842
                $buttons,
6843
                true
6844
            );
6845
        }
6846
6847
        if (api_is_platform_admin() && isset($extraField['authorlp'])) {
6848
            $actionsLeft .= Display::url(
6849
                Display::return_icon(
6850
                    'add-groups.png',
6851
                    get_lang('Author'),
6852
                    '',
6853
                    ICON_SIZE_MEDIUM
6854
                ),
6855
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6856
                    'action' => 'author_view',
6857
                    'lp_id' => $lpId,
6858
                ])
6859
            );
6860
        }
6861
6862
        $toolbar = Display::toolbarAction(
6863
            'actions-lp-controller',
6864
            [$actionsLeft, $actionsRight]
6865
        );
6866
6867
        if ($returnString) {
6868
            return $toolbar;
6869
        }
6870
6871
        echo $toolbar;
6872
    }
6873
6874
    /**
6875
     * Creates the default learning path folder.
6876
     *
6877
     * @param array $course
6878
     * @param int   $creatorId
6879
     *
6880
     * @return bool
6881
     */
6882
    public static function generate_learning_path_folder($course, $creatorId = 0)
6883
    {
6884
        // Creating learning_path folder
6885
        $dir = '/learning_path';
6886
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6887
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6888
6889
        $folder = false;
6890
        if (!is_dir($filepath.'/'.$dir)) {
6891
            $folderData = create_unexisting_directory(
6892
                $course,
6893
                $creatorId,
6894
                0,
6895
                null,
6896
                0,
6897
                $filepath,
6898
                $dir,
6899
                get_lang('LearningPaths'),
6900
                0
6901
            );
6902
            if (!empty($folderData)) {
6903
                $folder = true;
6904
            }
6905
        } else {
6906
            $folder = true;
6907
        }
6908
6909
        return $folder;
6910
    }
6911
6912
    /**
6913
     * @param array  $course
6914
     * @param string $lp_name
6915
     * @param int    $creatorId
6916
     *
6917
     * @return array
6918
     */
6919
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6920
    {
6921
        $filepath = '';
6922
        $dir = '/learning_path/';
6923
6924
        if (empty($lp_name)) {
6925
            $lp_name = $this->name;
6926
        }
6927
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6928
        $folder = self::generate_learning_path_folder($course, $creatorId);
6929
6930
        // Limits title size
6931
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6932
        $dir = $dir.$title;
6933
6934
        // Creating LP folder
6935
        $documentId = null;
6936
        if ($folder) {
6937
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6938
            if (!is_dir($filepath.'/'.$dir)) {
6939
                $folderData = create_unexisting_directory(
6940
                    $course,
6941
                    $creatorId,
6942
                    0,
6943
                    0,
6944
                    0,
6945
                    $filepath,
6946
                    $dir,
6947
                    $lp_name
6948
                );
6949
                if (!empty($folderData)) {
6950
                    $folder = true;
6951
                }
6952
6953
                $documentId = $folderData['id'];
6954
            } else {
6955
                $folder = true;
6956
            }
6957
            $dir = $dir.'/';
6958
            if ($folder) {
6959
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6960
            }
6961
        }
6962
6963
        if (empty($documentId)) {
6964
            $dir = api_remove_trailing_slash($dir);
6965
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6966
        }
6967
6968
        $array = [
6969
            'dir' => $dir,
6970
            'filepath' => $filepath,
6971
            'folder' => $folder,
6972
            'id' => $documentId,
6973
        ];
6974
6975
        return $array;
6976
    }
6977
6978
    /**
6979
     * Create a new document //still needs some finetuning.
6980
     *
6981
     * @param array  $courseInfo
6982
     * @param string $content
6983
     * @param string $title
6984
     * @param string $extension
6985
     * @param int    $parentId
6986
     * @param int    $creatorId  creator id
6987
     *
6988
     * @return int
6989
     */
6990
    public function create_document(
6991
        $courseInfo,
6992
        $content = '',
6993
        $title = '',
6994
        $extension = 'html',
6995
        $parentId = 0,
6996
        $creatorId = 0
6997
    ) {
6998
        if (!empty($courseInfo)) {
6999
            $course_id = $courseInfo['real_id'];
7000
        } else {
7001
            $course_id = api_get_course_int_id();
7002
        }
7003
7004
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
7005
        $sessionId = api_get_session_id();
7006
7007
        // Generates folder
7008
        $result = $this->generate_lp_folder($courseInfo);
7009
        $dir = $result['dir'];
7010
7011
        if (empty($parentId) || $parentId == '/') {
7012
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
7013
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
7014
7015
            if ($parentId === '/') {
7016
                $dir = '/';
7017
            }
7018
7019
            // Please, do not modify this dirname formatting.
7020
            if (strstr($dir, '..')) {
7021
                $dir = '/';
7022
            }
7023
7024
            if (!empty($dir[0]) && $dir[0] == '.') {
7025
                $dir = substr($dir, 1);
7026
            }
7027
            if (!empty($dir[0]) && $dir[0] != '/') {
7028
                $dir = '/'.$dir;
7029
            }
7030
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7031
                $dir .= '/';
7032
            }
7033
        } else {
7034
            $parentInfo = DocumentManager::get_document_data_by_id(
7035
                $parentId,
7036
                $courseInfo['code']
7037
            );
7038
            if (!empty($parentInfo)) {
7039
                $dir = $parentInfo['path'].'/';
7040
            }
7041
        }
7042
7043
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7044
        if (!is_dir($filepath)) {
7045
            $dir = '/';
7046
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
7047
        }
7048
7049
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
7050
        // is already escaped twice when it gets here.
7051
        $originalTitle = !empty($title) ? $title : $_POST['title'];
7052
        if (!empty($title)) {
7053
            $title = api_replace_dangerous_char(stripslashes($title));
7054
        } else {
7055
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
7056
        }
7057
7058
        $title = disable_dangerous_file($title);
7059
        $filename = $title;
7060
        $content = !empty($content) ? $content : $_POST['content_lp'];
7061
        $tmp_filename = $filename;
7062
7063
        $i = 0;
7064
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
7065
            $tmp_filename = $filename.'_'.++$i;
7066
        }
7067
7068
        $filename = $tmp_filename.'.'.$extension;
7069
        if ($extension == 'html') {
7070
            $content = stripslashes($content);
7071
            $content = str_replace(
7072
                api_get_path(WEB_COURSE_PATH),
7073
                api_get_path(REL_PATH).'courses/',
7074
                $content
7075
            );
7076
7077
            // Change the path of mp3 to absolute.
7078
            // The first regexp deals with :// urls.
7079
            $content = preg_replace(
7080
                "|(flashvars=\"file=)([^:/]+)/|",
7081
                "$1".api_get_path(
7082
                    REL_COURSE_PATH
7083
                ).$courseInfo['path'].'/document/',
7084
                $content
7085
            );
7086
            // The second regexp deals with audio/ urls.
7087
            $content = preg_replace(
7088
                "|(flashvars=\"file=)([^/]+)/|",
7089
                "$1".api_get_path(
7090
                    REL_COURSE_PATH
7091
                ).$courseInfo['path'].'/document/$2/',
7092
                $content
7093
            );
7094
            // For flv player: To prevent edition problem with firefox,
7095
            // we have to use a strange tip (don't blame me please).
7096
            $content = str_replace(
7097
                '</body>',
7098
                '<style type="text/css">body{}</style></body>',
7099
                $content
7100
            );
7101
        }
7102
7103
        if (!file_exists($filepath.$filename)) {
7104
            if ($fp = @fopen($filepath.$filename, 'w')) {
7105
                fputs($fp, $content);
7106
                fclose($fp);
7107
7108
                $file_size = filesize($filepath.$filename);
7109
                $save_file_path = $dir.$filename;
7110
7111
                $document_id = add_document(
7112
                    $courseInfo,
7113
                    $save_file_path,
7114
                    'file',
7115
                    $file_size,
7116
                    $tmp_filename,
7117
                    '',
7118
                    0, //readonly
7119
                    true,
7120
                    null,
7121
                    $sessionId,
7122
                    $creatorId
7123
                );
7124
7125
                if ($document_id) {
7126
                    api_item_property_update(
7127
                        $courseInfo,
7128
                        TOOL_DOCUMENT,
7129
                        $document_id,
7130
                        'DocumentAdded',
7131
                        $creatorId,
7132
                        null,
7133
                        null,
7134
                        null,
7135
                        null,
7136
                        $sessionId
7137
                    );
7138
7139
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7140
                    $new_title = $originalTitle;
7141
7142
                    if ($new_comment || $new_title) {
7143
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7144
                        $ct = '';
7145
                        if ($new_comment) {
7146
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7147
                        }
7148
                        if ($new_title) {
7149
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7150
                        }
7151
7152
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7153
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7154
                        Database::query($sql);
7155
                    }
7156
                }
7157
7158
                return $document_id;
7159
            }
7160
        }
7161
    }
7162
7163
    /**
7164
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7165
     *
7166
     * @param array $_course array
7167
     */
7168
    public function edit_document($_course)
7169
    {
7170
        $course_id = api_get_course_int_id();
7171
        $urlAppend = api_get_configuration_value('url_append');
7172
        // Please, do not modify this dirname formatting.
7173
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7174
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7175
7176
        if (strstr($dir, '..')) {
7177
            $dir = '/';
7178
        }
7179
7180
        if (isset($dir[0]) && $dir[0] == '.') {
7181
            $dir = substr($dir, 1);
7182
        }
7183
7184
        if (isset($dir[0]) && $dir[0] != '/') {
7185
            $dir = '/'.$dir;
7186
        }
7187
7188
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7189
            $dir .= '/';
7190
        }
7191
7192
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7193
        if (!is_dir($filepath)) {
7194
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7195
        }
7196
7197
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7198
7199
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7200
            $document_id = (int) $_POST['path'];
7201
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7202
            if (empty($documentInfo)) {
7203
                // Try with iid
7204
                $table = Database::get_course_table(TABLE_DOCUMENT);
7205
                $sql = "SELECT id, path FROM $table
7206
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7207
                $res_doc = Database::query($sql);
7208
                $row = Database::fetch_array($res_doc);
7209
                if ($row) {
7210
                    $document_id = $row['id'];
7211
                    $documentPath = $row['path'];
7212
                }
7213
            } else {
7214
                $documentPath = $documentInfo['path'];
7215
            }
7216
7217
            $content = stripslashes($_POST['content_lp']);
7218
            $file = $filepath.$documentPath;
7219
7220
            if (!file_exists($file)) {
7221
                return false;
7222
            }
7223
7224
            if ($fp = @fopen($file, 'w')) {
7225
                $content = str_replace(
7226
                    api_get_path(WEB_COURSE_PATH),
7227
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7228
                    $content
7229
                );
7230
                // Change the path of mp3 to absolute.
7231
                // The first regexp deals with :// urls.
7232
                $content = preg_replace(
7233
                    "|(flashvars=\"file=)([^:/]+)/|",
7234
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7235
                    $content
7236
                );
7237
                // The second regexp deals with audio/ urls.
7238
                $content = preg_replace(
7239
                    "|(flashvars=\"file=)([^:/]+)/|",
7240
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7241
                    $content
7242
                );
7243
                fputs($fp, $content);
7244
                fclose($fp);
7245
7246
                $sql = "UPDATE $table_doc SET
7247
                            title='".Database::escape_string($_POST['title'])."'
7248
                        WHERE c_id = $course_id AND id = ".$document_id;
7249
                Database::query($sql);
7250
            }
7251
        }
7252
    }
7253
7254
    /**
7255
     * Displays the selected item, with a panel for manipulating the item.
7256
     *
7257
     * @param int    $item_id
7258
     * @param string $msg
7259
     * @param bool   $show_actions
7260
     *
7261
     * @return string
7262
     */
7263
    public function display_item($item_id, $msg = null, $show_actions = true)
7264
    {
7265
        $course_id = api_get_course_int_id();
7266
        $return = '';
7267
        if (is_numeric($item_id)) {
7268
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7269
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7270
                    WHERE lp.iid = ".intval($item_id);
7271
            $result = Database::query($sql);
7272
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7273
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7274
7275
                // Prevents wrong parent selection for document, see Bug#1251.
7276
                if ($row['item_type'] != 'dir') {
7277
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7278
                }
7279
7280
                if ($show_actions) {
7281
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7282
                }
7283
                $return .= '<div style="padding:10px;">';
7284
7285
                if ($msg != '') {
7286
                    $return .= $msg;
7287
                }
7288
7289
                $return .= '<h3>'.$row['title'].'</h3>';
7290
7291
                switch ($row['item_type']) {
7292
                    case TOOL_THREAD:
7293
                        $link = $this->rl_get_resource_link_for_learnpath(
7294
                            $course_id,
7295
                            $row['lp_id'],
7296
                            $item_id,
7297
                            0
7298
                        );
7299
                        $return .= Display::url(
7300
                            get_lang('GoToThread'),
7301
                            $link,
7302
                            ['class' => 'btn btn-primary']
7303
                        );
7304
                        break;
7305
                    case TOOL_FORUM:
7306
                        $return .= Display::url(
7307
                            get_lang('GoToForum'),
7308
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7309
                            ['class' => 'btn btn-primary']
7310
                        );
7311
                        break;
7312
                    case TOOL_QUIZ:
7313
                        if (!empty($row['path'])) {
7314
                            $exercise = new Exercise();
7315
                            $exercise->read($row['path']);
7316
                            $return .= $exercise->description.'<br />';
7317
                            $return .= Display::url(
7318
                                get_lang('GoToExercise'),
7319
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7320
                                ['class' => 'btn btn-primary']
7321
                            );
7322
                        }
7323
                        break;
7324
                    case TOOL_LP_FINAL_ITEM:
7325
                        $return .= $this->getSavedFinalItem();
7326
                        break;
7327
                    case TOOL_DOCUMENT:
7328
                    case TOOL_READOUT_TEXT:
7329
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7330
                        $sql_doc = "SELECT path FROM $tbl_doc
7331
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7332
                        $result = Database::query($sql_doc);
7333
                        $path_file = Database::result($result, 0, 0);
7334
                        $path_parts = pathinfo($path_file);
7335
                        // TODO: Correct the following naive comparisons.
7336
                        if (in_array($path_parts['extension'], [
7337
                            'html',
7338
                            'txt',
7339
                            'png',
7340
                            'jpg',
7341
                            'JPG',
7342
                            'jpeg',
7343
                            'JPEG',
7344
                            'gif',
7345
                            'swf',
7346
                            'pdf',
7347
                            'htm',
7348
                        ])) {
7349
                            $return .= $this->display_document($row['path'], true, true);
7350
                        }
7351
                        break;
7352
                    case TOOL_HOTPOTATOES:
7353
                        $return .= $this->display_document($row['path'], false, true);
7354
                        break;
7355
                }
7356
                $return .= '</div>';
7357
            }
7358
        }
7359
7360
        return $return;
7361
    }
7362
7363
    /**
7364
     * Shows the needed forms for editing a specific item.
7365
     *
7366
     * @param int $item_id
7367
     *
7368
     * @throws Exception
7369
     * @throws HTML_QuickForm_Error
7370
     *
7371
     * @return string
7372
     */
7373
    public function display_edit_item($item_id, $excludeExtraFields = [])
7374
    {
7375
        $course_id = api_get_course_int_id();
7376
        $return = '';
7377
        $item_id = (int) $item_id;
7378
7379
        if (empty($item_id)) {
7380
            return '';
7381
        }
7382
7383
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7384
        $sql = "SELECT * FROM $tbl_lp_item
7385
                WHERE iid = ".$item_id;
7386
        $res = Database::query($sql);
7387
        $row = Database::fetch_array($res);
7388
        switch ($row['item_type']) {
7389
            case 'dir':
7390
            case 'asset':
7391
            case 'sco':
7392
            if (isset($_GET['view']) && $_GET['view'] == 'build') {
7393
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7394
                $return .= $this->display_item_form(
7395
                        $row['item_type'],
7396
                        get_lang('EditCurrentChapter').' :',
7397
                        'edit',
7398
                        $item_id,
7399
                        $row
7400
                    );
7401
            } else {
7402
                $return .= $this->display_item_form(
7403
                        $row['item_type'],
7404
                        get_lang('EditCurrentChapter').' :',
7405
                        'edit_item',
7406
                        $item_id,
7407
                        $row
7408
                    );
7409
            }
7410
                break;
7411
            case TOOL_DOCUMENT:
7412
            case TOOL_READOUT_TEXT:
7413
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7414
                $sql = "SELECT lp.*, doc.path as dir
7415
                        FROM $tbl_lp_item as lp
7416
                        LEFT JOIN $tbl_doc as doc
7417
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7418
                        WHERE
7419
                            doc.c_id = $course_id AND
7420
                            lp.iid = ".$item_id;
7421
                $res_step = Database::query($sql);
7422
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7423
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7424
7425
                if ($row['item_type'] === TOOL_DOCUMENT) {
7426
                    $return .= $this->display_document_form(
7427
                        'edit',
7428
                        $item_id,
7429
                        $row_step,
7430
                        null,
7431
                        $excludeExtraFields
7432
                    );
7433
                }
7434
7435
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7436
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7437
                }
7438
                break;
7439
            case TOOL_LINK:
7440
                $linkId = (int) $row['path'];
7441
                if (!empty($linkId)) {
7442
                    $table = Database::get_course_table(TABLE_LINK);
7443
                    $sql = 'SELECT url FROM '.$table.'
7444
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7445
                    $res_link = Database::query($sql);
7446
                    $row_link = Database::fetch_array($res_link);
7447
                    if (empty($row_link)) {
7448
                        // Try with id
7449
                        $sql = 'SELECT url FROM '.$table.'
7450
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7451
                        $res_link = Database::query($sql);
7452
                        $row_link = Database::fetch_array($res_link);
7453
                    }
7454
7455
                    if (is_array($row_link)) {
7456
                        $row['url'] = $row_link['url'];
7457
                    }
7458
                }
7459
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7460
                $return .= $this->display_link_form('edit', $item_id, $row, null, $excludeExtraFields);
7461
                break;
7462
            case TOOL_LP_FINAL_ITEM:
7463
                Session::write('finalItem', true);
7464
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7465
                $sql = "SELECT lp.*, doc.path as dir
7466
                        FROM $tbl_lp_item as lp
7467
                        LEFT JOIN $tbl_doc as doc
7468
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7469
                        WHERE
7470
                            doc.c_id = $course_id AND
7471
                            lp.iid = ".$item_id;
7472
                $res_step = Database::query($sql);
7473
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7474
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7475
                $return .= $this->display_document_form(
7476
                    'edit',
7477
                    $item_id,
7478
                    $row_step,
7479
                    null,
7480
                    $excludeExtraFields
7481
                );
7482
                break;
7483
            case TOOL_QUIZ:
7484
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7485
                $return .= $this->display_quiz_form('edit', $item_id, $row, $excludeExtraFields);
7486
                break;
7487
            case TOOL_HOTPOTATOES:
7488
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7489
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7490
                break;
7491
            case TOOL_STUDENTPUBLICATION:
7492
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7493
                $return .= $this->display_student_publication_form('edit', $item_id, $row, null, $excludeExtraFields);
7494
                break;
7495
            case TOOL_FORUM:
7496
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7497
                $return .= $this->display_forum_form('edit', $item_id, $row, $excludeExtraFields);
7498
                break;
7499
            case TOOL_THREAD:
7500
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7501
                $return .= $this->display_thread_form('edit', $item_id, $row);
7502
                break;
7503
        }
7504
7505
        return $return;
7506
    }
7507
7508
    /**
7509
     * Function that displays a list with al the resources that
7510
     * could be added to the learning path.
7511
     *
7512
     * @throws Exception
7513
     * @throws HTML_QuickForm_Error
7514
     *
7515
     * @return bool
7516
     */
7517
    public function display_resources()
7518
    {
7519
        $course_code = api_get_course_id();
7520
7521
        // Get all the docs.
7522
        $documents = $this->get_documents(true);
7523
7524
        // Get all the exercises.
7525
        $exercises = $this->get_exercises();
7526
7527
        // Get all the links.
7528
        $links = $this->get_links();
7529
7530
        // Get all the student publications.
7531
        $works = $this->get_student_publications();
7532
7533
        // Get all the forums.
7534
        $forums = $this->get_forums(null, $course_code);
7535
7536
        // Get the final item form (see BT#11048) .
7537
        $finish = $this->getFinalItemForm();
7538
7539
        $headers = [
7540
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7541
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7542
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7543
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7544
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7545
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7546
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7547
        ];
7548
7549
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7550
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7551
7552
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7553
7554
        echo Display::tabs(
7555
            $headers,
7556
            [
7557
                $documents,
7558
                $exercises,
7559
                $links,
7560
                $works,
7561
                $forums,
7562
                $dir,
7563
                $finish,
7564
            ],
7565
            'resource_tab',
7566
            [],
7567
            [],
7568
            $selected
7569
        );
7570
7571
        return true;
7572
    }
7573
7574
    /**
7575
     * Returns the extension of a document.
7576
     *
7577
     * @param string $filename
7578
     *
7579
     * @return string Extension (part after the last dot)
7580
     */
7581
    public function get_extension($filename)
7582
    {
7583
        $explode = explode('.', $filename);
7584
7585
        return $explode[count($explode) - 1];
7586
    }
7587
7588
    /**
7589
     * Displays a document by id.
7590
     *
7591
     * @param int  $id
7592
     * @param bool $show_title
7593
     * @param bool $iframe
7594
     * @param bool $edit_link
7595
     *
7596
     * @return string
7597
     */
7598
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7599
    {
7600
        $_course = api_get_course_info();
7601
        $course_id = api_get_course_int_id();
7602
        $id = (int) $id;
7603
        $return = '';
7604
        $table = Database::get_course_table(TABLE_DOCUMENT);
7605
        $sql_doc = "SELECT * FROM $table
7606
                    WHERE c_id = $course_id AND iid = $id";
7607
        $res_doc = Database::query($sql_doc);
7608
        $row_doc = Database::fetch_array($res_doc);
7609
7610
        // TODO: Add a path filter.
7611
        if ($iframe) {
7612
            $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>';
7613
        } else {
7614
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7615
        }
7616
7617
        return $return;
7618
    }
7619
7620
    /**
7621
     * Return HTML form to add/edit a quiz.
7622
     *
7623
     * @param string $action     Action (add/edit)
7624
     * @param int    $id         Item ID if already exists
7625
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7626
     *
7627
     * @throws Exception
7628
     *
7629
     * @return string HTML form
7630
     */
7631
    public function display_quiz_form(
7632
        $action = 'add',
7633
        $id = 0,
7634
        $extra_info = '',
7635
        $excludeExtraFields = []
7636
    ) {
7637
        $course_id = api_get_course_int_id();
7638
        $id = (int) $id;
7639
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7640
7641
        if ($id != 0 && is_array($extra_info)) {
7642
            $item_title = $extra_info['title'];
7643
            $item_description = $extra_info['description'];
7644
        } elseif (is_numeric($extra_info)) {
7645
            $sql = "SELECT title, description
7646
                    FROM $tbl_quiz
7647
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7648
7649
            $result = Database::query($sql);
7650
            $row = Database::fetch_array($result);
7651
            $item_title = $row['title'];
7652
            $item_description = $row['description'];
7653
        } else {
7654
            $item_title = '';
7655
            $item_description = '';
7656
        }
7657
        $item_title = Security::remove_XSS($item_title);
7658
        $item_description = Security::remove_XSS($item_description);
7659
7660
        $parent = 0;
7661
        if ($id != 0 && is_array($extra_info)) {
7662
            $parent = $extra_info['parent_item_id'];
7663
        }
7664
7665
        $arrLP = $this->getItemsForForm();
7666
        $this->tree_array($arrLP);
7667
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7668
        unset($this->arrMenu);
7669
7670
        $form = new FormValidator(
7671
            'quiz_form',
7672
            'POST',
7673
            $this->getCurrentBuildingModeURL()
7674
        );
7675
        $defaults = [];
7676
7677
        if ($action === 'add') {
7678
            $legend = get_lang('CreateTheExercise');
7679
        } elseif ($action === 'move') {
7680
            $legend = get_lang('MoveTheCurrentExercise');
7681
        } else {
7682
            $legend = get_lang('EditCurrentExecice');
7683
        }
7684
7685
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7686
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7687
        }
7688
7689
        $form->addHeader($legend);
7690
7691
        if ($action != 'move') {
7692
            $this->setItemTitle($form);
7693
            $defaults['title'] = $item_title;
7694
        }
7695
7696
        // Select for Parent item, root or chapter
7697
        $selectParent = $form->addSelect(
7698
            'parent',
7699
            get_lang('Parent'),
7700
            [],
7701
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7702
        );
7703
        $selectParent->addOption($this->name, 0);
7704
7705
        $arrHide = [
7706
            $id,
7707
        ];
7708
        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...
7709
            if ($action != 'add') {
7710
                if (
7711
                    ($arrLP[$i]['item_type'] == 'dir') &&
7712
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7713
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7714
                ) {
7715
                    $selectParent->addOption(
7716
                        $arrLP[$i]['title'],
7717
                        $arrLP[$i]['id'],
7718
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7719
                    );
7720
7721
                    if ($parent == $arrLP[$i]['id']) {
7722
                        $selectParent->setSelected($arrLP[$i]['id']);
7723
                    }
7724
                } else {
7725
                    $arrHide[] = $arrLP[$i]['id'];
7726
                }
7727
            } else {
7728
                if ($arrLP[$i]['item_type'] == 'dir') {
7729
                    $selectParent->addOption(
7730
                        $arrLP[$i]['title'],
7731
                        $arrLP[$i]['id'],
7732
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7733
                    );
7734
7735
                    if ($parent == $arrLP[$i]['id']) {
7736
                        $selectParent->setSelected($arrLP[$i]['id']);
7737
                    }
7738
                }
7739
            }
7740
        }
7741
7742
        if (is_array($arrLP)) {
7743
            reset($arrLP);
7744
        }
7745
7746
        $selectPrevious = $form->addSelect(
7747
            'previous',
7748
            get_lang('Position'),
7749
            [],
7750
            ['id' => 'previous']
7751
        );
7752
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7753
7754
        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...
7755
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7756
                $arrLP[$i]['id'] != $id
7757
            ) {
7758
                $selectPrevious->addOption(
7759
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7760
                    $arrLP[$i]['id']
7761
                );
7762
7763
                if (is_array($extra_info)) {
7764
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7765
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7766
                    }
7767
                } elseif ($action == 'add') {
7768
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7769
                }
7770
            }
7771
        }
7772
7773
        if ($action != 'move') {
7774
            $arrHide = [];
7775
            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...
7776
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7777
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7778
                }
7779
            }
7780
        }
7781
7782
        if ('edit' === $action) {
7783
            $extraField = new ExtraField('lp_item');
7784
            $extraField->addElements($form, $id, $excludeExtraFields);
7785
        }
7786
7787
        if ($action === 'add') {
7788
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7789
        } else {
7790
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7791
        }
7792
7793
        if ($action === 'move') {
7794
            $form->addHidden('title', $item_title);
7795
            $form->addHidden('description', $item_description);
7796
        }
7797
7798
        if (is_numeric($extra_info)) {
7799
            $form->addHidden('path', $extra_info);
7800
        } elseif (is_array($extra_info)) {
7801
            $form->addHidden('path', $extra_info['path']);
7802
        }
7803
7804
        $form->addHidden('type', TOOL_QUIZ);
7805
        $form->addHidden('post_time', time());
7806
        $this->setAuthorLpItem($form);
7807
        $form->setDefaults($defaults);
7808
7809
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7810
    }
7811
7812
    /**
7813
     * Addition of Hotpotatoes tests.
7814
     *
7815
     * @param string $action
7816
     * @param int    $id         Internal ID of the item
7817
     * @param string $extra_info
7818
     *
7819
     * @return string HTML structure to display the hotpotatoes addition formular
7820
     */
7821
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7822
    {
7823
        $course_id = api_get_course_int_id();
7824
        $uploadPath = DIR_HOTPOTATOES;
7825
7826
        if ($id != 0 && is_array($extra_info)) {
7827
            $item_title = stripslashes($extra_info['title']);
7828
            $item_description = stripslashes($extra_info['description']);
7829
        } elseif (is_numeric($extra_info)) {
7830
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7831
7832
            $sql = "SELECT * FROM $TBL_DOCUMENT
7833
                    WHERE
7834
                        c_id = $course_id AND
7835
                        path LIKE '".$uploadPath."/%/%htm%' AND
7836
                        iid = ".(int) $extra_info."
7837
                    ORDER BY iid ASC";
7838
7839
            $res_hot = Database::query($sql);
7840
            $row = Database::fetch_array($res_hot);
7841
7842
            $item_title = $row['title'];
7843
            $item_description = $row['description'];
7844
7845
            if (!empty($row['comment'])) {
7846
                $item_title = $row['comment'];
7847
            }
7848
        } else {
7849
            $item_title = '';
7850
            $item_description = '';
7851
        }
7852
7853
        $parent = 0;
7854
        if ($id != 0 && is_array($extra_info)) {
7855
            $parent = $extra_info['parent_item_id'];
7856
        }
7857
7858
        $arrLP = $this->getItemsForForm();
7859
        $legend = '<legend>';
7860
        if ($action == 'add') {
7861
            $legend .= get_lang('CreateTheExercise');
7862
        } elseif ($action == 'move') {
7863
            $legend .= get_lang('MoveTheCurrentExercise');
7864
        } else {
7865
            $legend .= get_lang('EditCurrentExecice');
7866
        }
7867
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7868
            $legend .= Display:: return_message(
7869
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7870
            );
7871
        }
7872
        $legend .= '</legend>';
7873
7874
        $return = '<form method="POST">';
7875
        $return .= $legend;
7876
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7877
        $return .= '<tr>';
7878
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7879
        $return .= '<td class="input">';
7880
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7881
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7882
        $arrHide = [$id];
7883
7884
        if (count($arrLP) > 0) {
7885
            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...
7886
                if ($action != 'add') {
7887
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7888
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7889
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7890
                    ) {
7891
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7892
                    } else {
7893
                        $arrHide[] = $arrLP[$i]['id'];
7894
                    }
7895
                } else {
7896
                    if ($arrLP[$i]['item_type'] == 'dir') {
7897
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7898
                    }
7899
                }
7900
            }
7901
            reset($arrLP);
7902
        }
7903
7904
        $return .= '</select>';
7905
        $return .= '</td>';
7906
        $return .= '</tr>';
7907
        $return .= '<tr>';
7908
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7909
        $return .= '<td class="input">';
7910
        $return .= '<select id="previous" name="previous" size="1">';
7911
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7912
7913
        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...
7914
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7915
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7916
                    $selected = 'selected="selected" ';
7917
                } elseif ($action == 'add') {
7918
                    $selected = 'selected="selected" ';
7919
                } else {
7920
                    $selected = '';
7921
                }
7922
7923
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7924
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7925
            }
7926
        }
7927
7928
        $return .= '</select>';
7929
        $return .= '</td>';
7930
        $return .= '</tr>';
7931
7932
        if ($action != 'move') {
7933
            $return .= '<tr>';
7934
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7935
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7936
            $return .= '</tr>';
7937
            $id_prerequisite = 0;
7938
            if (is_array($arrLP) && count($arrLP) > 0) {
7939
                foreach ($arrLP as $key => $value) {
7940
                    if ($value['id'] == $id) {
7941
                        $id_prerequisite = $value['prerequisite'];
7942
                        break;
7943
                    }
7944
                }
7945
7946
                $arrHide = [];
7947
                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...
7948
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7949
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7950
                    }
7951
                }
7952
            }
7953
        }
7954
7955
        $return .= '<tr>';
7956
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7957
            get_lang('SaveHotpotatoes').'</button></td>';
7958
        $return .= '</tr>';
7959
        $return .= '</table>';
7960
7961
        if ($action == 'move') {
7962
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7963
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7964
        }
7965
7966
        if (is_numeric($extra_info)) {
7967
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7968
        } elseif (is_array($extra_info)) {
7969
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7970
        }
7971
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7972
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7973
        $return .= '</form>';
7974
7975
        return $return;
7976
    }
7977
7978
    /**
7979
     * Return the form to display the forum edit/add option.
7980
     *
7981
     * @param string $action
7982
     * @param int    $id         ID of the lp_item if already exists
7983
     * @param string $extra_info
7984
     *
7985
     * @throws Exception
7986
     *
7987
     * @return string HTML form
7988
     */
7989
    public function display_forum_form(
7990
        $action = 'add',
7991
        $id = 0,
7992
        $extra_info = '',
7993
        $excludeExtraFields = []
7994
    ) {
7995
        $course_id = api_get_course_int_id();
7996
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7997
7998
        $item_title = '';
7999
        $item_description = '';
8000
8001
        if ($id != 0 && is_array($extra_info)) {
8002
            $item_title = stripslashes($extra_info['title']);
8003
        } elseif (is_numeric($extra_info)) {
8004
            $sql = "SELECT forum_title as title, forum_comment as comment
8005
                    FROM $tbl_forum
8006
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
8007
8008
            $result = Database::query($sql);
8009
            $row = Database::fetch_array($result);
8010
8011
            $item_title = $row['title'];
8012
            $item_description = $row['comment'];
8013
        }
8014
        $parent = 0;
8015
        if ($id != 0 && is_array($extra_info)) {
8016
            $parent = $extra_info['parent_item_id'];
8017
        }
8018
        $arrLP = $this->getItemsForForm();
8019
        $this->tree_array($arrLP);
8020
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8021
        unset($this->arrMenu);
8022
8023
        if ($action == 'add') {
8024
            $legend = get_lang('CreateTheForum');
8025
        } elseif ($action == 'move') {
8026
            $legend = get_lang('MoveTheCurrentForum');
8027
        } else {
8028
            $legend = get_lang('EditCurrentForum');
8029
        }
8030
8031
        $form = new FormValidator(
8032
            'forum_form',
8033
            'POST',
8034
            $this->getCurrentBuildingModeURL()
8035
        );
8036
        $defaults = [];
8037
8038
        $form->addHeader($legend);
8039
8040
        if ($action != 'move') {
8041
            $this->setItemTitle($form);
8042
            $defaults['title'] = $item_title;
8043
        }
8044
8045
        $selectParent = $form->addSelect(
8046
            'parent',
8047
            get_lang('Parent'),
8048
            [],
8049
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
8050
        );
8051
        $selectParent->addOption($this->name, 0);
8052
        $arrHide = [
8053
            $id,
8054
        ];
8055
        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...
8056
            if ($action != 'add') {
8057
                if ($arrLP[$i]['item_type'] == 'dir' &&
8058
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8059
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8060
                ) {
8061
                    $selectParent->addOption(
8062
                        $arrLP[$i]['title'],
8063
                        $arrLP[$i]['id'],
8064
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8065
                    );
8066
8067
                    if ($parent == $arrLP[$i]['id']) {
8068
                        $selectParent->setSelected($arrLP[$i]['id']);
8069
                    }
8070
                } else {
8071
                    $arrHide[] = $arrLP[$i]['id'];
8072
                }
8073
            } else {
8074
                if ($arrLP[$i]['item_type'] == 'dir') {
8075
                    $selectParent->addOption(
8076
                        $arrLP[$i]['title'],
8077
                        $arrLP[$i]['id'],
8078
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8079
                    );
8080
8081
                    if ($parent == $arrLP[$i]['id']) {
8082
                        $selectParent->setSelected($arrLP[$i]['id']);
8083
                    }
8084
                }
8085
            }
8086
        }
8087
8088
        if (is_array($arrLP)) {
8089
            reset($arrLP);
8090
        }
8091
8092
        $selectPrevious = $form->addSelect(
8093
            'previous',
8094
            get_lang('Position'),
8095
            [],
8096
            ['id' => 'previous', 'class' => 'learnpath_item_form']
8097
        );
8098
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8099
8100
        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...
8101
            if ($arrLP[$i]['parent_item_id'] == $parent &&
8102
                $arrLP[$i]['id'] != $id
8103
            ) {
8104
                $selectPrevious->addOption(
8105
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8106
                    $arrLP[$i]['id']
8107
                );
8108
8109
                if (isset($extra_info['previous_item_id']) &&
8110
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
8111
                ) {
8112
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8113
                } elseif ($action == 'add') {
8114
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8115
                }
8116
            }
8117
        }
8118
8119
        if ($action != 'move') {
8120
            $id_prerequisite = 0;
8121
            if (is_array($arrLP)) {
8122
                foreach ($arrLP as $key => $value) {
8123
                    if ($value['id'] == $id) {
8124
                        $id_prerequisite = $value['prerequisite'];
8125
                        break;
8126
                    }
8127
                }
8128
            }
8129
8130
            $arrHide = [];
8131
            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...
8132
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8133
                    if (isset($extra_info['previous_item_id']) &&
8134
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
8135
                    ) {
8136
                        $s_selected_position = $arrLP[$i]['id'];
8137
                    } elseif ($action == 'add') {
8138
                        $s_selected_position = 0;
8139
                    }
8140
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8141
                }
8142
            }
8143
        }
8144
8145
        if ('edit' === $action) {
8146
            $extraField = new ExtraField('lp_item');
8147
            $extraField->addElements($form, $id, $excludeExtraFields);
8148
        }
8149
8150
        if ($action == 'add') {
8151
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8152
        } else {
8153
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8154
        }
8155
8156
        if ($action == 'move') {
8157
            $form->addHidden('title', $item_title);
8158
            $form->addHidden('description', $item_description);
8159
        }
8160
8161
        if (is_numeric($extra_info)) {
8162
            $form->addHidden('path', $extra_info);
8163
        } elseif (is_array($extra_info)) {
8164
            $form->addHidden('path', $extra_info['path']);
8165
        }
8166
        $form->addHidden('type', TOOL_FORUM);
8167
        $form->addHidden('post_time', time());
8168
        $this->setAuthorLpItem($form);
8169
        $form->setDefaults($defaults);
8170
8171
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8172
    }
8173
8174
    /**
8175
     * Return HTML form to add/edit forum threads.
8176
     *
8177
     * @param string $action
8178
     * @param int    $id         Item ID if already exists in learning path
8179
     * @param string $extra_info
8180
     *
8181
     * @throws Exception
8182
     *
8183
     * @return string HTML form
8184
     */
8185
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8186
    {
8187
        $course_id = api_get_course_int_id();
8188
        if (empty($course_id)) {
8189
            return null;
8190
        }
8191
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8192
8193
        $item_title = '';
8194
        $item_description = '';
8195
        if ($id != 0 && is_array($extra_info)) {
8196
            $item_title = stripslashes($extra_info['title']);
8197
        } elseif (is_numeric($extra_info)) {
8198
            $sql = "SELECT thread_title as title FROM $tbl_forum
8199
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8200
8201
            $result = Database::query($sql);
8202
            $row = Database::fetch_array($result);
8203
8204
            $item_title = $row['title'];
8205
            $item_description = '';
8206
        }
8207
8208
        $parent = 0;
8209
        if ($id != 0 && is_array($extra_info)) {
8210
            $parent = $extra_info['parent_item_id'];
8211
        }
8212
8213
        $arrLP = $this->getItemsForForm();
8214
        $this->tree_array($arrLP);
8215
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8216
        unset($this->arrMenu);
8217
8218
        $form = new FormValidator(
8219
            'thread_form',
8220
            'POST',
8221
            $this->getCurrentBuildingModeURL()
8222
        );
8223
        $defaults = [];
8224
8225
        if ($action == 'add') {
8226
            $legend = get_lang('CreateTheForum');
8227
        } elseif ($action == 'move') {
8228
            $legend = get_lang('MoveTheCurrentForum');
8229
        } else {
8230
            $legend = get_lang('EditCurrentForum');
8231
        }
8232
8233
        $form->addHeader($legend);
8234
        $selectParent = $form->addSelect(
8235
            'parent',
8236
            get_lang('Parent'),
8237
            [],
8238
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8239
        );
8240
        $selectParent->addOption($this->name, 0);
8241
8242
        $arrHide = [
8243
            $id,
8244
        ];
8245
8246
        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...
8247
            if ($action != 'add') {
8248
                if (
8249
                    ($arrLP[$i]['item_type'] == 'dir') &&
8250
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8251
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8252
                ) {
8253
                    $selectParent->addOption(
8254
                        $arrLP[$i]['title'],
8255
                        $arrLP[$i]['id'],
8256
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8257
                    );
8258
8259
                    if ($parent == $arrLP[$i]['id']) {
8260
                        $selectParent->setSelected($arrLP[$i]['id']);
8261
                    }
8262
                } else {
8263
                    $arrHide[] = $arrLP[$i]['id'];
8264
                }
8265
            } else {
8266
                if ($arrLP[$i]['item_type'] == 'dir') {
8267
                    $selectParent->addOption(
8268
                        $arrLP[$i]['title'],
8269
                        $arrLP[$i]['id'],
8270
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8271
                    );
8272
8273
                    if ($parent == $arrLP[$i]['id']) {
8274
                        $selectParent->setSelected($arrLP[$i]['id']);
8275
                    }
8276
                }
8277
            }
8278
        }
8279
8280
        if ($arrLP != null) {
8281
            reset($arrLP);
8282
        }
8283
8284
        $selectPrevious = $form->addSelect(
8285
            'previous',
8286
            get_lang('Position'),
8287
            [],
8288
            ['id' => 'previous']
8289
        );
8290
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8291
8292
        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...
8293
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8294
                $selectPrevious->addOption(
8295
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8296
                    $arrLP[$i]['id']
8297
                );
8298
8299
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8300
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8301
                } elseif ($action == 'add') {
8302
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8303
                }
8304
            }
8305
        }
8306
8307
        if ($action != 'move') {
8308
            $this->setItemTitle($form);
8309
            $defaults['title'] = $item_title;
8310
8311
            $id_prerequisite = 0;
8312
            if ($arrLP != null) {
8313
                foreach ($arrLP as $key => $value) {
8314
                    if ($value['id'] == $id) {
8315
                        $id_prerequisite = $value['prerequisite'];
8316
                        break;
8317
                    }
8318
                }
8319
            }
8320
8321
            $arrHide = [];
8322
            $s_selected_position = 0;
8323
            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...
8324
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8325
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8326
                        $s_selected_position = $arrLP[$i]['id'];
8327
                    } elseif ($action == 'add') {
8328
                        $s_selected_position = 0;
8329
                    }
8330
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8331
                }
8332
            }
8333
8334
            $selectPrerequisites = $form->addSelect(
8335
                'prerequisites',
8336
                get_lang('LearnpathPrerequisites'),
8337
                [],
8338
                ['id' => 'prerequisites']
8339
            );
8340
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8341
8342
            foreach ($arrHide as $key => $value) {
8343
                $selectPrerequisites->addOption($value['value'], $key);
8344
8345
                if ($key == $s_selected_position && $action == 'add') {
8346
                    $selectPrerequisites->setSelected($key);
8347
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8348
                    $selectPrerequisites->setSelected($key);
8349
                }
8350
            }
8351
        }
8352
8353
        if ('edit' === $action) {
8354
            $extraField = new ExtraField('lp_item');
8355
            $extraField->addElements($form, $id);
8356
        }
8357
8358
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8359
8360
        if ($action == 'move') {
8361
            $form->addHidden('title', $item_title);
8362
            $form->addHidden('description', $item_description);
8363
        }
8364
8365
        if (is_numeric($extra_info)) {
8366
            $form->addHidden('path', $extra_info);
8367
        } elseif (is_array($extra_info)) {
8368
            $form->addHidden('path', $extra_info['path']);
8369
        }
8370
8371
        $form->addHidden('type', TOOL_THREAD);
8372
        $form->addHidden('post_time', time());
8373
        $this->setAuthorLpItem($form);
8374
        $form->setDefaults($defaults);
8375
8376
        return $form->returnForm();
8377
    }
8378
8379
    /**
8380
     * Return the HTML form to display an item (generally a dir item).
8381
     *
8382
     * @param string $item_type
8383
     * @param string $title
8384
     * @param string $action
8385
     * @param int    $id
8386
     * @param string $extra_info
8387
     *
8388
     * @throws Exception
8389
     * @throws HTML_QuickForm_Error
8390
     *
8391
     * @return string HTML form
8392
     */
8393
    public function display_item_form(
8394
        $item_type,
8395
        $title = '',
8396
        $action = 'add_item',
8397
        $id = 0,
8398
        $extra_info = 'new'
8399
    ) {
8400
        $_course = api_get_course_info();
8401
8402
        global $charset;
8403
8404
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8405
        $item_title = '';
8406
        $item_description = '';
8407
        $item_path_fck = '';
8408
8409
        $parent = 0;
8410
        $previousId = null;
8411
        if ($id != 0 && is_array($extra_info)) {
8412
            $item_title = $extra_info['title'];
8413
            $item_description = $extra_info['description'];
8414
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8415
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8416
            $parent = $extra_info['parent_item_id'];
8417
            $previousId = $extra_info['previous_item_id'];
8418
        }
8419
8420
        if ($extra_info instanceof learnpathItem) {
8421
            $item_title = $extra_info->get_title();
8422
            $item_description = $extra_info->get_description();
8423
            $path = $extra_info->get_path();
8424
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8425
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8426
            $parent = $extra_info->get_parent();
8427
            $previousId = $extra_info->previous;
8428
        }
8429
8430
        $id = (int) $id;
8431
        $sql = "SELECT * FROM $tbl_lp_item
8432
                WHERE
8433
                    lp_id = ".$this->lp_id." AND
8434
                    iid != $id";
8435
8436
        if ($item_type === 'dir') {
8437
            $sql .= " AND parent_item_id = 0";
8438
        }
8439
8440
        $result = Database::query($sql);
8441
        $arrLP = [];
8442
        while ($row = Database::fetch_array($result)) {
8443
            $arrLP[] = [
8444
                'id' => $row['iid'],
8445
                'item_type' => $row['item_type'],
8446
                'title' => $this->cleanItemTitle($row['title']),
8447
                'title_raw' => $row['title'],
8448
                'path' => $row['path'],
8449
                'description' => $row['description'],
8450
                'parent_item_id' => $row['parent_item_id'],
8451
                'previous_item_id' => $row['previous_item_id'],
8452
                'next_item_id' => $row['next_item_id'],
8453
                'max_score' => $row['max_score'],
8454
                'min_score' => $row['min_score'],
8455
                'mastery_score' => $row['mastery_score'],
8456
                'prerequisite' => $row['prerequisite'],
8457
                'display_order' => $row['display_order'],
8458
            ];
8459
        }
8460
8461
        $this->tree_array($arrLP);
8462
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8463
        unset($this->arrMenu);
8464
8465
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8466
8467
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8468
        $defaults['title'] = api_html_entity_decode(
8469
            $item_title,
8470
            ENT_QUOTES,
8471
            $charset
8472
        );
8473
        $defaults['description'] = $item_description;
8474
8475
        $form->addHeader($title);
8476
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8477
        $arrHide[0]['padding'] = 20;
8478
        $charset = api_get_system_encoding();
8479
        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...
8480
            if ($action != 'add') {
8481
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8482
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8483
                ) {
8484
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8485
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8486
                    if ($parent == $arrLP[$i]['id']) {
8487
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8488
                    }
8489
                }
8490
            } else {
8491
                if ($arrLP[$i]['item_type'] === 'dir') {
8492
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8493
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8494
                    if ($parent == $arrLP[$i]['id']) {
8495
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8496
                    }
8497
                }
8498
            }
8499
        }
8500
8501
        if ($action !== 'move') {
8502
            $this->setItemTitle($form);
8503
        } else {
8504
            $form->addElement('hidden', 'title');
8505
        }
8506
8507
        $parentSelect = $form->addElement(
8508
            'select',
8509
            'parent',
8510
            get_lang('Parent'),
8511
            '',
8512
            [
8513
                'id' => 'idParent',
8514
                'onchange' => 'javascript: load_cbo(this.value);',
8515
            ]
8516
        );
8517
8518
        foreach ($arrHide as $key => $value) {
8519
            $parentSelect->addOption(
8520
                $value['value'],
8521
                $key,
8522
                'style="padding-left:'.$value['padding'].'px;"'
8523
            );
8524
            $lastPosition = $key;
8525
        }
8526
8527
        if (!empty($s_selected_parent)) {
8528
            $parentSelect->setSelected($s_selected_parent);
8529
        }
8530
8531
        if (is_array($arrLP)) {
8532
            reset($arrLP);
8533
        }
8534
8535
        $arrHide = [];
8536
        // POSITION
8537
        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...
8538
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8539
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8540
                //this is the same!
8541
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8542
                    $s_selected_position = $arrLP[$i]['id'];
8543
                } elseif ($action === 'add') {
8544
                    $s_selected_position = $arrLP[$i]['id'];
8545
                }
8546
8547
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8548
            }
8549
        }
8550
8551
        $position = $form->addElement(
8552
            'select',
8553
            'previous',
8554
            get_lang('Position'),
8555
            '',
8556
            ['id' => 'previous']
8557
        );
8558
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8559
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8560
8561
        $lastPosition = null;
8562
        foreach ($arrHide as $key => $value) {
8563
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8564
            $lastPosition = $key;
8565
        }
8566
8567
        if (!empty($s_selected_position)) {
8568
            $position->setSelected($s_selected_position);
8569
        }
8570
8571
        // When new chapter add at the end
8572
        if ($action === 'add_item') {
8573
            $position->setSelected($lastPosition);
8574
        }
8575
8576
        if (is_array($arrLP)) {
8577
            reset($arrLP);
8578
        }
8579
8580
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8581
8582
        //fix in order to use the tab
8583
        if ($item_type === 'dir') {
8584
            $form->addElement('hidden', 'type', 'dir');
8585
        }
8586
8587
        $extension = null;
8588
        if (!empty($item_path)) {
8589
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8590
        }
8591
8592
        //assets can't be modified
8593
        //$item_type == 'asset' ||
8594
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8595
            if ($item_type === 'sco') {
8596
                $form->addElement(
8597
                    'html',
8598
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8599
                );
8600
            }
8601
            $renderer = $form->defaultRenderer();
8602
            $renderer->setElementTemplate(
8603
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8604
                'content_lp'
8605
            );
8606
8607
            $relative_prefix = '';
8608
            $editor_config = [
8609
                'ToolbarSet' => 'LearningPathDocuments',
8610
                'Width' => '100%',
8611
                'Height' => '500',
8612
                'FullPage' => true,
8613
                'CreateDocumentDir' => $relative_prefix,
8614
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8615
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8616
            ];
8617
8618
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8619
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8620
            $defaults['content_lp'] = file_get_contents($content_path);
8621
        }
8622
8623
        if (!empty($id)) {
8624
            $form->addHidden('id', $id);
8625
        }
8626
8627
        $form->addElement('hidden', 'type', $item_type);
8628
        $form->addElement('hidden', 'post_time', time());
8629
        $form->setDefaults($defaults);
8630
8631
        return $form->returnForm();
8632
    }
8633
8634
    /**
8635
     * @return string
8636
     */
8637
    public function getCurrentBuildingModeURL()
8638
    {
8639
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8640
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8641
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8642
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8643
8644
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8645
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8646
8647
        return $currentUrl;
8648
    }
8649
8650
    /**
8651
     * Returns the form to update or create a document.
8652
     *
8653
     * @param string        $action     (add/edit)
8654
     * @param int           $id         ID of the lp_item (if already exists)
8655
     * @param mixed         $extra_info Integer if document ID, string if info ('new')
8656
     * @param learnpathItem $item
8657
     *
8658
     * @return string HTML form
8659
     */
8660
    public function display_document_form(
8661
        $action = 'add',
8662
        $id = 0,
8663
        $extra_info = 'new',
8664
        $item = null,
8665
        $excludeExtraFields = []
8666
    ) {
8667
        $course_id = api_get_course_int_id();
8668
        $_course = api_get_course_info();
8669
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8670
8671
        $no_display_edit_textarea = false;
8672
        //If action==edit document
8673
        //We don't display the document form if it's not an editable document (html or txt file)
8674
        if ($action === 'edit') {
8675
            if (is_array($extra_info)) {
8676
                $path_parts = pathinfo($extra_info['dir']);
8677
                if ($path_parts['extension'] !== 'txt' && $path_parts['extension'] !== 'html') {
8678
                    $no_display_edit_textarea = true;
8679
                }
8680
            }
8681
        }
8682
        $no_display_add = false;
8683
        // If action==add an existing document
8684
        // We don't display the document form if it's not an editable document (html or txt file).
8685
        if ($action === 'add') {
8686
            if (is_numeric($extra_info)) {
8687
                $extra_info = (int) $extra_info;
8688
                $sql_doc = "SELECT path FROM $tbl_doc
8689
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8690
                $result = Database::query($sql_doc);
8691
                $path_file = Database::result($result, 0, 0);
8692
                $path_parts = pathinfo($path_file);
8693
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8694
                    $no_display_add = true;
8695
                }
8696
            }
8697
        }
8698
8699
        $item_title = '';
8700
        $item_description = '';
8701
        if ($id != 0 && is_array($extra_info)) {
8702
            $item_title = stripslashes($extra_info['title']);
8703
            $item_description = stripslashes($extra_info['description']);
8704
            if (empty($item_title)) {
8705
                $path_parts = pathinfo($extra_info['path']);
8706
                $item_title = stripslashes($path_parts['filename']);
8707
            }
8708
        } elseif (is_numeric($extra_info)) {
8709
            $sql = "SELECT path, title FROM $tbl_doc
8710
                    WHERE
8711
                        c_id = ".$course_id." AND
8712
                        iid = ".intval($extra_info);
8713
            $result = Database::query($sql);
8714
            $row = Database::fetch_array($result);
8715
            $item_title = $row['title'];
8716
            $item_title = str_replace('_', ' ', $item_title);
8717
            if (empty($item_title)) {
8718
                $path_parts = pathinfo($row['path']);
8719
                $item_title = stripslashes($path_parts['filename']);
8720
            }
8721
        }
8722
8723
        $return = '<legend>';
8724
        $parent = 0;
8725
        if ($id != 0 && is_array($extra_info)) {
8726
            $parent = $extra_info['parent_item_id'];
8727
        }
8728
8729
        $selectedPosition = 0;
8730
        if (is_array($extra_info) && isset($extra_info['previous_item_id'])) {
8731
            $selectedPosition = $extra_info['previous_item_id'];
8732
        }
8733
8734
        if ($item instanceof learnpathItem) {
8735
            $item_title = stripslashes($item->get_title());
8736
            $item_description = stripslashes($item->get_description());
8737
            $selectedPosition = $item->previous;
8738
            $parent = $item->parent;
8739
        }
8740
8741
        $arrLP = $this->getItemsForForm();
8742
        $this->tree_array($arrLP);
8743
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8744
        unset($this->arrMenu);
8745
8746
        if ($action === 'add') {
8747
            $return .= get_lang('CreateTheDocument');
8748
        } elseif ($action === 'move') {
8749
            $return .= get_lang('MoveTheCurrentDocument');
8750
        } else {
8751
            $return .= get_lang('EditTheCurrentDocument');
8752
        }
8753
        $return .= '</legend>';
8754
8755
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8756
            $return .= Display::return_message(
8757
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8758
                false
8759
            );
8760
        }
8761
        $form = new FormValidator(
8762
            'form',
8763
            'POST',
8764
            $this->getCurrentBuildingModeURL(),
8765
            '',
8766
            ['enctype' => 'multipart/form-data']
8767
        );
8768
        $defaults['title'] = Security::remove_XSS($item_title);
8769
        if (empty($item_title)) {
8770
            $defaults['title'] = Security::remove_XSS($item_title);
8771
        }
8772
        $defaults['description'] = $item_description;
8773
        $form->addElement('html', $return);
8774
8775
        if ($action !== 'move') {
8776
            $data = $this->generate_lp_folder($_course);
8777
            if ($action !== 'edit') {
8778
                $folders = DocumentManager::get_all_document_folders(
8779
                    $_course,
8780
                    0,
8781
                    true
8782
                );
8783
                DocumentManager::build_directory_selector(
8784
                    $folders,
8785
                    '',
8786
                    [],
8787
                    true,
8788
                    $form,
8789
                    'directory_parent_id'
8790
                );
8791
            }
8792
8793
            if (isset($data['id'])) {
8794
                $defaults['directory_parent_id'] = $data['id'];
8795
            }
8796
            $this->setItemTitle($form);
8797
        }
8798
8799
        $arrHide[0]['value'] = $this->name;
8800
        $arrHide[0]['padding'] = 20;
8801
8802
        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...
8803
            if ($action !== 'add') {
8804
                if ($arrLP[$i]['item_type'] === 'dir' &&
8805
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8806
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8807
                ) {
8808
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8809
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8810
                }
8811
            } else {
8812
                if ($arrLP[$i]['item_type'] == 'dir') {
8813
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8814
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8815
                }
8816
            }
8817
        }
8818
8819
        $parentSelect = $form->addSelect(
8820
            'parent',
8821
            get_lang('Parent'),
8822
            [],
8823
            [
8824
                'id' => 'idParent',
8825
                'onchange' => 'javascript: load_cbo(this.value);',
8826
            ]
8827
        );
8828
8829
        $my_count = 0;
8830
        foreach ($arrHide as $key => $value) {
8831
            if ($my_count != 0) {
8832
                // The LP name is also the first section and is not in the same charset like the other sections.
8833
                $value['value'] = Security::remove_XSS($value['value']);
8834
                $parentSelect->addOption(
8835
                    $value['value'],
8836
                    $key,
8837
                    'style="padding-left:'.$value['padding'].'px;"'
8838
                );
8839
            } else {
8840
                $value['value'] = Security::remove_XSS($value['value']);
8841
                $parentSelect->addOption(
8842
                    $value['value'],
8843
                    $key,
8844
                    'style="padding-left:'.$value['padding'].'px;"'
8845
                );
8846
            }
8847
            $my_count++;
8848
        }
8849
8850
        if (!empty($id)) {
8851
            $parentSelect->setSelected($parent);
8852
        } else {
8853
            $parent_item_id = Session::read('parent_item_id', 0);
8854
            $parentSelect->setSelected($parent_item_id);
8855
        }
8856
8857
        if (is_array($arrLP)) {
8858
            reset($arrLP);
8859
        }
8860
8861
        $arrHide = [];
8862
        // POSITION
8863
        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...
8864
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8865
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8866
            ) {
8867
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8868
            }
8869
        }
8870
8871
        $position = $form->addSelect(
8872
            'previous',
8873
            get_lang('Position'),
8874
            [],
8875
            ['id' => 'previous']
8876
        );
8877
8878
        $position->addOption(get_lang('FirstPosition'), 0);
8879
        foreach ($arrHide as $key => $value) {
8880
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8881
            $position->addOption(
8882
                $value['value'],
8883
                $key,
8884
                'style="padding-left:'.$padding.'px;"'
8885
            );
8886
        }
8887
        $position->setSelected($selectedPosition);
8888
8889
        if (is_array($arrLP)) {
8890
            reset($arrLP);
8891
        }
8892
8893
        if ('edit' === $action) {
8894
            $extraField = new ExtraField('lp_item');
8895
            $extraField->addElements($form, $id, $excludeExtraFields);
8896
        }
8897
8898
        if ($action !== 'move') {
8899
            $arrHide = [];
8900
            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...
8901
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8902
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8903
                ) {
8904
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8905
                }
8906
            }
8907
8908
            if (!$no_display_add) {
8909
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8910
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8911
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8912
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8913
                ) {
8914
                    if (isset($_POST['content'])) {
8915
                        $content = stripslashes($_POST['content']);
8916
                    } elseif (is_array($extra_info)) {
8917
                        //If it's an html document or a text file
8918
                        if (!$no_display_edit_textarea) {
8919
                            $content = $this->display_document(
8920
                                $extra_info['path'],
8921
                                false,
8922
                                false
8923
                            );
8924
                        }
8925
                    } elseif (is_numeric($extra_info)) {
8926
                        $content = $this->display_document(
8927
                            $extra_info,
8928
                            false,
8929
                            false
8930
                        );
8931
                    } else {
8932
                        $content = '';
8933
                    }
8934
8935
                    if (!$no_display_edit_textarea) {
8936
                        // We need to calculate here some specific settings for the online editor.
8937
                        // The calculated settings work for documents in the Documents tool
8938
                        // (on the root or in subfolders).
8939
                        // For documents in native scorm packages it is unclear whether the
8940
                        // online editor should be activated or not.
8941
8942
                        // A new document, it is in the root of the repository.
8943
                        if (is_array($extra_info) && $extra_info != 'new') {
8944
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8945
                            $relative_path = explode('/', $extra_info['dir']);
8946
                            $cnt = count($relative_path) - 2;
8947
                            if ($cnt < 0) {
8948
                                $cnt = 0;
8949
                            }
8950
                            $relative_prefix = str_repeat('../', $cnt);
8951
                            $relative_path = array_slice($relative_path, 1, $cnt);
8952
                            $relative_path = implode('/', $relative_path);
8953
                            if (strlen($relative_path) > 0) {
8954
                                $relative_path = $relative_path.'/';
8955
                            }
8956
                        } else {
8957
                            $result = $this->generate_lp_folder($_course);
8958
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8959
                            $relative_prefix = '../../';
8960
                        }
8961
8962
                        $editor_config = [
8963
                            'ToolbarSet' => 'LearningPathDocuments',
8964
                            'Width' => '100%',
8965
                            'Height' => '500',
8966
                            'FullPage' => true,
8967
                            'CreateDocumentDir' => $relative_prefix,
8968
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8969
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8970
                        ];
8971
8972
                        if ($_GET['action'] === 'add_item') {
8973
                            $class = 'add';
8974
                            $text = get_lang('LPCreateDocument');
8975
                        } else {
8976
                            if ($_GET['action'] === 'edit_item') {
8977
                                $class = 'save';
8978
                                $text = get_lang('SaveDocument');
8979
                            }
8980
                        }
8981
8982
                        $form->addButtonSave($text, 'submit_button');
8983
                        $renderer = $form->defaultRenderer();
8984
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8985
                        $form->addElement('html', '<div class="editor-lp">');
8986
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8987
                        $form->addElement('html', '</div>');
8988
                        $defaults['content_lp'] = $content;
8989
                    }
8990
                } elseif (is_numeric($extra_info)) {
8991
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8992
8993
                    $return = $this->display_document($extra_info, true, true, true);
8994
                    $form->addElement('html', $return);
8995
                }
8996
            }
8997
        }
8998
        if (isset($extra_info['item_type']) &&
8999
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
9000
        ) {
9001
            $parentSelect->freeze();
9002
            $position->freeze();
9003
        }
9004
9005
        if ($action === 'move') {
9006
            $form->addElement('hidden', 'title', $item_title);
9007
            $form->addElement('hidden', 'description', $item_description);
9008
        }
9009
        if (is_numeric($extra_info)) {
9010
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9011
            $form->addElement('hidden', 'path', $extra_info);
9012
        } elseif (is_array($extra_info)) {
9013
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9014
            $form->addElement('hidden', 'path', $extra_info['path']);
9015
        }
9016
9017
        if ($item instanceof learnpathItem) {
9018
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9019
            $form->addElement('hidden', 'path', $item->path);
9020
        }
9021
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
9022
        $form->addElement('hidden', 'post_time', time());
9023
        $this->setAuthorLpItem($form);
9024
        $form->setDefaults($defaults);
9025
9026
        return $form->returnForm();
9027
    }
9028
9029
    /**
9030
     * Returns the form to update or create a read-out text.
9031
     *
9032
     * @param string $action     "add" or "edit"
9033
     * @param int    $id         ID of the lp_item (if already exists)
9034
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
9035
     *
9036
     * @throws Exception
9037
     * @throws HTML_QuickForm_Error
9038
     *
9039
     * @return string HTML form
9040
     */
9041
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
9042
    {
9043
        $course_id = api_get_course_int_id();
9044
        $_course = api_get_course_info();
9045
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
9046
9047
        $no_display_edit_textarea = false;
9048
        //If action==edit document
9049
        //We don't display the document form if it's not an editable document (html or txt file)
9050
        if ($action == 'edit') {
9051
            if (is_array($extra_info)) {
9052
                $path_parts = pathinfo($extra_info['dir']);
9053
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
9054
                    $no_display_edit_textarea = true;
9055
                }
9056
            }
9057
        }
9058
        $no_display_add = false;
9059
9060
        $item_title = '';
9061
        $item_description = '';
9062
        if ($id != 0 && is_array($extra_info)) {
9063
            $item_title = stripslashes($extra_info['title']);
9064
            $item_description = stripslashes($extra_info['description']);
9065
            $item_terms = stripslashes($extra_info['terms']);
9066
            if (empty($item_title)) {
9067
                $path_parts = pathinfo($extra_info['path']);
9068
                $item_title = stripslashes($path_parts['filename']);
9069
            }
9070
        } elseif (is_numeric($extra_info)) {
9071
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
9072
            $result = Database::query($sql);
9073
            $row = Database::fetch_array($result);
9074
            $item_title = $row['title'];
9075
            $item_title = str_replace('_', ' ', $item_title);
9076
            if (empty($item_title)) {
9077
                $path_parts = pathinfo($row['path']);
9078
                $item_title = stripslashes($path_parts['filename']);
9079
            }
9080
        }
9081
9082
        $parent = 0;
9083
        if ($id != 0 && is_array($extra_info)) {
9084
            $parent = $extra_info['parent_item_id'];
9085
        }
9086
9087
        $arrLP = $this->getItemsForForm();
9088
        $this->tree_array($arrLP);
9089
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9090
        unset($this->arrMenu);
9091
9092
        if ($action === 'add') {
9093
            $formHeader = get_lang('CreateTheDocument');
9094
        } else {
9095
            $formHeader = get_lang('EditTheCurrentDocument');
9096
        }
9097
9098
        if ('edit' === $action) {
9099
            $urlAudioIcon = Display::url(
9100
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
9101
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
9102
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
9103
            );
9104
        } else {
9105
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
9106
        }
9107
9108
        $form = new FormValidator(
9109
            'frm_add_reading',
9110
            'POST',
9111
            $this->getCurrentBuildingModeURL(),
9112
            '',
9113
            ['enctype' => 'multipart/form-data']
9114
        );
9115
        $form->addHeader($formHeader);
9116
        $form->addHtml(
9117
            Display::return_message(
9118
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
9119
                'normal',
9120
                false
9121
            )
9122
        );
9123
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
9124
        $defaults['description'] = $item_description;
9125
9126
        $data = $this->generate_lp_folder($_course);
9127
9128
        if ($action != 'edit') {
9129
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
9130
            DocumentManager::build_directory_selector(
9131
                $folders,
9132
                '',
9133
                [],
9134
                true,
9135
                $form,
9136
                'directory_parent_id'
9137
            );
9138
        }
9139
9140
        if (isset($data['id'])) {
9141
            $defaults['directory_parent_id'] = $data['id'];
9142
        }
9143
        $this->setItemTitle($form);
9144
9145
        $arrHide[0]['value'] = $this->name;
9146
        $arrHide[0]['padding'] = 20;
9147
9148
        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...
9149
            if ($action != 'add') {
9150
                if ($arrLP[$i]['item_type'] == 'dir' &&
9151
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9152
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9153
                ) {
9154
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9155
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9156
                }
9157
            } else {
9158
                if ($arrLP[$i]['item_type'] == 'dir') {
9159
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9160
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9161
                }
9162
            }
9163
        }
9164
9165
        $parent_select = $form->addSelect(
9166
            'parent',
9167
            get_lang('Parent'),
9168
            [],
9169
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9170
        );
9171
9172
        $my_count = 0;
9173
        foreach ($arrHide as $key => $value) {
9174
            if ($my_count != 0) {
9175
                // The LP name is also the first section and is not in the same charset like the other sections.
9176
                $value['value'] = Security::remove_XSS($value['value']);
9177
                $parent_select->addOption(
9178
                    $value['value'],
9179
                    $key,
9180
                    'style="padding-left:'.$value['padding'].'px;"'
9181
                );
9182
            } else {
9183
                $value['value'] = Security::remove_XSS($value['value']);
9184
                $parent_select->addOption(
9185
                    $value['value'],
9186
                    $key,
9187
                    'style="padding-left:'.$value['padding'].'px;"'
9188
                );
9189
            }
9190
            $my_count++;
9191
        }
9192
9193
        if (!empty($id)) {
9194
            $parent_select->setSelected($parent);
9195
        } else {
9196
            $parent_item_id = Session::read('parent_item_id', 0);
9197
            $parent_select->setSelected($parent_item_id);
9198
        }
9199
9200
        if (is_array($arrLP)) {
9201
            reset($arrLP);
9202
        }
9203
9204
        $arrHide = [];
9205
        $s_selected_position = null;
9206
9207
        // POSITION
9208
        $lastPosition = null;
9209
9210
        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...
9211
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9212
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9213
            ) {
9214
                if ((isset($extra_info['previous_item_id']) &&
9215
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9216
                ) {
9217
                    $s_selected_position = $arrLP[$i]['id'];
9218
                }
9219
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9220
            }
9221
            $lastPosition = $arrLP[$i]['id'];
9222
        }
9223
9224
        if (empty($s_selected_position)) {
9225
            $s_selected_position = $lastPosition;
9226
        }
9227
9228
        $position = $form->addSelect(
9229
            'previous',
9230
            get_lang('Position'),
9231
            []
9232
        );
9233
        $position->addOption(get_lang('FirstPosition'), 0);
9234
9235
        foreach ($arrHide as $key => $value) {
9236
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9237
            $position->addOption(
9238
                $value['value'],
9239
                $key,
9240
                'style="padding-left:'.$padding.'px;"'
9241
            );
9242
        }
9243
        $position->setSelected($s_selected_position);
9244
9245
        if (is_array($arrLP)) {
9246
            reset($arrLP);
9247
        }
9248
9249
        if ('edit' === $action) {
9250
            $extraField = new ExtraField('lp_item');
9251
            $extraField->addElements($form, $id);
9252
        }
9253
9254
        $arrHide = [];
9255
9256
        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...
9257
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9258
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9259
            ) {
9260
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9261
            }
9262
        }
9263
9264
        if (!$no_display_add) {
9265
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9266
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9267
9268
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9269
                if (!$no_display_edit_textarea) {
9270
                    $content = '';
9271
9272
                    if (isset($_POST['content'])) {
9273
                        $content = stripslashes($_POST['content']);
9274
                    } elseif (is_array($extra_info)) {
9275
                        $content = $this->display_document($extra_info['path'], false, false);
9276
                    } elseif (is_numeric($extra_info)) {
9277
                        $content = $this->display_document($extra_info, false, false);
9278
                    }
9279
9280
                    // A new document, it is in the root of the repository.
9281
                    if (is_array($extra_info) && $extra_info != 'new') {
9282
                    } else {
9283
                        $this->generate_lp_folder($_course);
9284
                    }
9285
9286
                    if ($_GET['action'] == 'add_item') {
9287
                        $text = get_lang('LPCreateDocument');
9288
                    } else {
9289
                        $text = get_lang('SaveDocument');
9290
                    }
9291
9292
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9293
                    $form
9294
                        ->defaultRenderer()
9295
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9296
                    $form->addButtonSave($text, 'submit_button');
9297
                    $defaults['content_lp'] = $content;
9298
                }
9299
            } elseif (is_numeric($extra_info)) {
9300
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9301
9302
                $return = $this->display_document($extra_info, true, true, true);
9303
                $form->addElement('html', $return);
9304
            }
9305
        }
9306
9307
        if (is_numeric($extra_info)) {
9308
            $form->addElement('hidden', 'path', $extra_info);
9309
        } elseif (is_array($extra_info)) {
9310
            $form->addElement('hidden', 'path', $extra_info['path']);
9311
        }
9312
9313
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9314
        $form->addElement('hidden', 'post_time', time());
9315
        $this->setAuthorLpItem($form);
9316
        $form->setDefaults($defaults);
9317
9318
        return $form->returnForm();
9319
    }
9320
9321
    /**
9322
     * @param array  $courseInfo
9323
     * @param string $content
9324
     * @param string $title
9325
     * @param int    $parentId
9326
     *
9327
     * @throws \Doctrine\ORM\ORMException
9328
     * @throws \Doctrine\ORM\OptimisticLockException
9329
     * @throws \Doctrine\ORM\TransactionRequiredException
9330
     *
9331
     * @return int
9332
     */
9333
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9334
    {
9335
        $creatorId = api_get_user_id();
9336
        $sessionId = api_get_session_id();
9337
9338
        // Generates folder
9339
        $result = $this->generate_lp_folder($courseInfo);
9340
        $dir = $result['dir'];
9341
9342
        if (empty($parentId) || $parentId == '/') {
9343
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9344
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9345
9346
            if ($parentId === '/') {
9347
                $dir = '/';
9348
            }
9349
9350
            // Please, do not modify this dirname formatting.
9351
            if (strstr($dir, '..')) {
9352
                $dir = '/';
9353
            }
9354
9355
            if (!empty($dir[0]) && $dir[0] == '.') {
9356
                $dir = substr($dir, 1);
9357
            }
9358
            if (!empty($dir[0]) && $dir[0] != '/') {
9359
                $dir = '/'.$dir;
9360
            }
9361
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9362
                $dir .= '/';
9363
            }
9364
        } else {
9365
            $parentInfo = DocumentManager::get_document_data_by_id(
9366
                $parentId,
9367
                $courseInfo['code']
9368
            );
9369
            if (!empty($parentInfo)) {
9370
                $dir = $parentInfo['path'].'/';
9371
            }
9372
        }
9373
9374
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9375
9376
        if (!is_dir($filepath)) {
9377
            $dir = '/';
9378
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9379
        }
9380
9381
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9382
9383
        if (!empty($title)) {
9384
            $title = api_replace_dangerous_char(stripslashes($title));
9385
        } else {
9386
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9387
        }
9388
9389
        $title = disable_dangerous_file($title);
9390
        $filename = $title;
9391
        $content = !empty($content) ? $content : $_POST['content_lp'];
9392
        $tmpFileName = $filename;
9393
9394
        $i = 0;
9395
        while (file_exists($filepath.$tmpFileName.'.html')) {
9396
            $tmpFileName = $filename.'_'.++$i;
9397
        }
9398
9399
        $filename = $tmpFileName.'.html';
9400
        $content = stripslashes($content);
9401
9402
        if (file_exists($filepath.$filename)) {
9403
            return 0;
9404
        }
9405
9406
        $putContent = file_put_contents($filepath.$filename, $content);
9407
9408
        if ($putContent === false) {
9409
            return 0;
9410
        }
9411
9412
        $fileSize = filesize($filepath.$filename);
9413
        $saveFilePath = $dir.$filename;
9414
9415
        $documentId = add_document(
9416
            $courseInfo,
9417
            $saveFilePath,
9418
            'file',
9419
            $fileSize,
9420
            $tmpFileName,
9421
            '',
9422
            0, //readonly
9423
            true,
9424
            null,
9425
            $sessionId,
9426
            $creatorId
9427
        );
9428
9429
        if (!$documentId) {
9430
            return 0;
9431
        }
9432
9433
        api_item_property_update(
9434
            $courseInfo,
9435
            TOOL_DOCUMENT,
9436
            $documentId,
9437
            'DocumentAdded',
9438
            $creatorId,
9439
            null,
9440
            null,
9441
            null,
9442
            null,
9443
            $sessionId
9444
        );
9445
9446
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9447
        $newTitle = $originalTitle;
9448
9449
        if ($newComment || $newTitle) {
9450
            $em = Database::getManager();
9451
9452
            /** @var CDocument $doc */
9453
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9454
9455
            if ($newComment) {
9456
                $doc->setComment($newComment);
9457
            }
9458
9459
            if ($newTitle) {
9460
                $doc->setTitle($newTitle);
9461
            }
9462
9463
            $em->persist($doc);
9464
            $em->flush();
9465
        }
9466
9467
        return $documentId;
9468
    }
9469
9470
    /**
9471
     * Return HTML form to add/edit a link item.
9472
     *
9473
     * @param string $action     (add/edit)
9474
     * @param int    $id         Item ID if exists
9475
     * @param mixed  $extra_info
9476
     *
9477
     * @throws Exception
9478
     * @throws HTML_QuickForm_Error
9479
     *
9480
     * @return string HTML form
9481
     */
9482
    public function display_link_form(
9483
        $action = 'add',
9484
        $id = 0,
9485
        $extra_info = '',
9486
        $item = null,
9487
        $excludeExtraFields = []
9488
    ) {
9489
        $course_id = api_get_course_int_id();
9490
        $tbl_link = Database::get_course_table(TABLE_LINK);
9491
9492
        $item_title = '';
9493
        $item_description = '';
9494
        $item_url = '';
9495
9496
        $previousId = 0;
9497
        if ($id != 0 && is_array($extra_info)) {
9498
            $item_title = stripslashes($extra_info['title']);
9499
            $item_description = stripslashes($extra_info['description']);
9500
            $item_url = stripslashes($extra_info['url']);
9501
            $previousId = $extra_info['previous_item_id'];
9502
        } elseif (is_numeric($extra_info)) {
9503
            $extra_info = (int) $extra_info;
9504
            $sql = "SELECT title, description, url
9505
                    FROM $tbl_link
9506
                    WHERE c_id = $course_id AND iid = $extra_info";
9507
            $result = Database::query($sql);
9508
            $row = Database::fetch_array($result);
9509
            $item_title = $row['title'];
9510
            $item_description = $row['description'];
9511
            $item_url = $row['url'];
9512
        }
9513
9514
        if ($item instanceof learnpathItem) {
9515
            $previousId = $extra_info->previous;
9516
        }
9517
9518
        $form = new FormValidator(
9519
            'edit_link',
9520
            'POST',
9521
            $this->getCurrentBuildingModeURL()
9522
        );
9523
        $defaults = [];
9524
        $parent = 0;
9525
        if ($id != 0 && is_array($extra_info)) {
9526
            $parent = $extra_info['parent_item_id'];
9527
        }
9528
9529
        $arrLP = $this->getItemsForForm();
9530
9531
        $this->tree_array($arrLP);
9532
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9533
        unset($this->arrMenu);
9534
9535
        if ($action === 'add') {
9536
            $legend = get_lang('CreateTheLink');
9537
        } elseif ($action === 'move') {
9538
            $legend = get_lang('MoveCurrentLink');
9539
        } else {
9540
            $legend = get_lang('EditCurrentLink');
9541
        }
9542
9543
        $form->addHeader($legend);
9544
9545
        if ($action !== 'move') {
9546
            $this->setItemTitle($form);
9547
            $defaults['title'] = $item_title;
9548
        }
9549
9550
        $selectParent = $form->addSelect(
9551
            'parent',
9552
            get_lang('Parent'),
9553
            [],
9554
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9555
        );
9556
        $selectParent->addOption($this->name, 0);
9557
        $arrHide = [
9558
            $id,
9559
        ];
9560
9561
        $parent_item_id = Session::read('parent_item_id', 0);
9562
9563
        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...
9564
            if ($action != 'add') {
9565
                if (
9566
                    ($arrLP[$i]['item_type'] == 'dir') &&
9567
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9568
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9569
                ) {
9570
                    $selectParent->addOption(
9571
                        $arrLP[$i]['title'],
9572
                        $arrLP[$i]['id'],
9573
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9574
                    );
9575
9576
                    if ($parent == $arrLP[$i]['id']) {
9577
                        $selectParent->setSelected($arrLP[$i]['id']);
9578
                    }
9579
                } else {
9580
                    $arrHide[] = $arrLP[$i]['id'];
9581
                }
9582
            } else {
9583
                if ($arrLP[$i]['item_type'] == 'dir') {
9584
                    $selectParent->addOption(
9585
                        $arrLP[$i]['title'],
9586
                        $arrLP[$i]['id'],
9587
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9588
                    );
9589
9590
                    if ($parent_item_id == $arrLP[$i]['id']) {
9591
                        $selectParent->setSelected($arrLP[$i]['id']);
9592
                    }
9593
                }
9594
            }
9595
        }
9596
9597
        if (is_array($arrLP)) {
9598
            reset($arrLP);
9599
        }
9600
9601
        $selectPrevious = $form->addSelect(
9602
            'previous',
9603
            get_lang('Position'),
9604
            [],
9605
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9606
        );
9607
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9608
9609
        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...
9610
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9611
                $selectPrevious->addOption(
9612
                    $arrLP[$i]['title'],
9613
                    $arrLP[$i]['id']
9614
                );
9615
9616
                if ($previousId == $arrLP[$i]['id']) {
9617
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9618
                } elseif ($action === 'add') {
9619
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9620
                }
9621
            }
9622
        }
9623
9624
        if ($action !== 'move') {
9625
            $urlAttributes = ['class' => 'learnpath_item_form'];
9626
9627
            if (is_numeric($extra_info)) {
9628
                $urlAttributes['disabled'] = 'disabled';
9629
            }
9630
9631
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9632
            $defaults['url'] = $item_url;
9633
            $arrHide = [];
9634
            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...
9635
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9636
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9637
                }
9638
            }
9639
        }
9640
9641
        if ('edit' === $action) {
9642
            $extraField = new ExtraField('lp_item');
9643
            $extraField->addElements($form, $id, $excludeExtraFields);
9644
        }
9645
9646
        if ($action === 'add') {
9647
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9648
        } else {
9649
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9650
        }
9651
9652
        if ($action === 'move') {
9653
            $form->addHidden('title', $item_title);
9654
            $form->addHidden('description', $item_description);
9655
        }
9656
9657
        if (is_numeric($extra_info)) {
9658
            $form->addHidden('path', $extra_info);
9659
        } elseif (is_array($extra_info)) {
9660
            $form->addHidden('path', $extra_info['path']);
9661
        }
9662
        $form->addHidden('type', TOOL_LINK);
9663
        $form->addHidden('post_time', time());
9664
        $this->setAuthorLpItem($form);
9665
        $form->setDefaults($defaults);
9666
9667
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9668
    }
9669
9670
    /**
9671
     * Return HTML form to add/edit a student publication (work).
9672
     *
9673
     * @param string $action
9674
     * @param int    $id         Item ID if already exists
9675
     * @param string $extra_info
9676
     *
9677
     * @throws Exception
9678
     *
9679
     * @return string HTML form
9680
     */
9681
    public function display_student_publication_form(
9682
        $action = 'add',
9683
        $id = 0,
9684
        $extra_info = '',
9685
        $item = null,
9686
        $excludeExtraFields = []
9687
    ) {
9688
        $course_id = api_get_course_int_id();
9689
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9690
9691
        $item_title = get_lang('Student_publication');
9692
        $previousId = 0;
9693
        if ($id != 0 && is_array($extra_info)) {
9694
            $item_title = stripslashes($extra_info['title']);
9695
            $item_description = stripslashes($extra_info['description']);
9696
            $previousId = $extra_info['previous_item_id'];
9697
        } elseif (is_numeric($extra_info)) {
9698
            $extra_info = (int) $extra_info;
9699
            $sql = "SELECT title, description
9700
                    FROM $tbl_publication
9701
                    WHERE c_id = $course_id AND id = ".$extra_info;
9702
9703
            $result = Database::query($sql);
9704
            $row = Database::fetch_array($result);
9705
            if ($row) {
9706
                $item_title = $row['title'];
9707
            }
9708
        }
9709
9710
        if ($item instanceof learnpathItem) {
9711
            $item_title = $item->get_title();
9712
            $item_description = $item->get_description();
9713
            $previousId = $item->previous;
9714
        }
9715
9716
        $parent = 0;
9717
        if ($id != 0 && is_array($extra_info)) {
9718
            $parent = $extra_info['parent_item_id'];
9719
        }
9720
9721
        $arrLP = $this->getItemsForForm();
9722
9723
        $this->tree_array($arrLP);
9724
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9725
        unset($this->arrMenu);
9726
9727
        $form = new FormValidator('frm_student_publication', 'post', '#');
9728
9729
        if ($action === 'add') {
9730
            $form->addHeader(get_lang('Student_publication'));
9731
        } elseif ($action === 'move') {
9732
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9733
        } else {
9734
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9735
        }
9736
9737
        if ($action !== 'move') {
9738
            $this->setItemTitle($form);
9739
        }
9740
9741
        $parentSelect = $form->addSelect(
9742
            'parent',
9743
            get_lang('Parent'),
9744
            ['0' => $this->name],
9745
            [
9746
                'onchange' => 'javascript: load_cbo(this.value);',
9747
                'class' => 'learnpath_item_form',
9748
                'id' => 'idParent',
9749
            ]
9750
        );
9751
9752
        $arrHide = [$id];
9753
        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...
9754
            if ($action != 'add') {
9755
                if (
9756
                    ($arrLP[$i]['item_type'] == 'dir') &&
9757
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9758
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9759
                ) {
9760
                    $parentSelect->addOption(
9761
                        $arrLP[$i]['title'],
9762
                        $arrLP[$i]['id'],
9763
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9764
                    );
9765
9766
                    if ($parent == $arrLP[$i]['id']) {
9767
                        $parentSelect->setSelected($arrLP[$i]['id']);
9768
                    }
9769
                } else {
9770
                    $arrHide[] = $arrLP[$i]['id'];
9771
                }
9772
            } else {
9773
                if ($arrLP[$i]['item_type'] === 'dir') {
9774
                    $parentSelect->addOption(
9775
                        $arrLP[$i]['title'],
9776
                        $arrLP[$i]['id'],
9777
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9778
                    );
9779
9780
                    if ($parent == $arrLP[$i]['id']) {
9781
                        $parentSelect->setSelected($arrLP[$i]['id']);
9782
                    }
9783
                }
9784
            }
9785
        }
9786
9787
        if (is_array($arrLP)) {
9788
            reset($arrLP);
9789
        }
9790
9791
        $previousSelect = $form->addSelect(
9792
            'previous',
9793
            get_lang('Position'),
9794
            ['0' => get_lang('FirstPosition')],
9795
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9796
        );
9797
9798
        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...
9799
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9800
                $previousSelect->addOption(
9801
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9802
                    $arrLP[$i]['id']
9803
                );
9804
9805
                if ($previousId == $arrLP[$i]['id']) {
9806
                    $previousSelect->setSelected($arrLP[$i]['id']);
9807
                } elseif ($action === 'add') {
9808
                    $previousSelect->setSelected($arrLP[$i]['id']);
9809
                }
9810
            }
9811
        }
9812
9813
        if ('edit' === $action) {
9814
            $extraField = new ExtraField('lp_item');
9815
            $extraField->addElements($form, $id, $excludeExtraFields);
9816
        }
9817
9818
        if ($action === 'add') {
9819
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9820
        } else {
9821
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9822
        }
9823
9824
        if ($action === 'move') {
9825
            $form->addHidden('title', $item_title);
9826
            $form->addHidden('description', $item_description);
9827
        }
9828
9829
        if (is_numeric($extra_info)) {
9830
            $form->addHidden('path', $extra_info);
9831
        } elseif (is_array($extra_info)) {
9832
            $form->addHidden('path', $extra_info['path']);
9833
        }
9834
9835
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9836
        $form->addHidden('post_time', time());
9837
        $this->setAuthorLpItem($form);
9838
        $form->setDefaults(['title' => $item_title]);
9839
9840
        $return = '<div class="sectioncomment">';
9841
        $return .= $form->returnForm();
9842
        $return .= '</div>';
9843
9844
        return $return;
9845
    }
9846
9847
    /**
9848
     * Displays the menu for manipulating a step.
9849
     *
9850
     * @param int    $item_id
9851
     * @param string $item_type
9852
     *
9853
     * @return string
9854
     */
9855
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9856
    {
9857
        $_course = api_get_course_info();
9858
        $course_code = api_get_course_id();
9859
        $item_id = (int) $item_id;
9860
9861
        $return = '<div class="actions">';
9862
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9863
        $sql = "SELECT * FROM $tbl_lp_item
9864
                WHERE iid = ".$item_id;
9865
        $result = Database::query($sql);
9866
        $row = Database::fetch_assoc($result);
9867
9868
        $audio_player = null;
9869
        // We display an audio player if needed.
9870
        if (!empty($row['audio'])) {
9871
            $audio = learnpathItem::fixAudio($row['audio']);
9872
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9873
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9874
                            <audio src="'.$webAudioPath.'" controls>
9875
                            </div><br />';
9876
        }
9877
9878
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9879
9880
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9881
            $return .= Display::url(
9882
                Display::return_icon(
9883
                    'edit.png',
9884
                    get_lang('Edit'),
9885
                    [],
9886
                    ICON_SIZE_SMALL
9887
                ),
9888
                $url.'&action=edit_item&path_item='.$row['path']
9889
            );
9890
9891
            $return .= Display::url(
9892
                Display::return_icon(
9893
                    'move.png',
9894
                    get_lang('Move'),
9895
                    [],
9896
                    ICON_SIZE_SMALL
9897
                ),
9898
                $url.'&action=move_item'
9899
            );
9900
        }
9901
9902
        // Commented for now as prerequisites cannot be added to chapters.
9903
        if ($item_type != 'dir') {
9904
            $return .= Display::url(
9905
                Display::return_icon(
9906
                    'accept.png',
9907
                    get_lang('LearnpathPrerequisites'),
9908
                    [],
9909
                    ICON_SIZE_SMALL
9910
                ),
9911
                $url.'&action=edit_item_prereq'
9912
            );
9913
        }
9914
        $return .= Display::url(
9915
            Display::return_icon(
9916
                'delete.png',
9917
                get_lang('Delete'),
9918
                [],
9919
                ICON_SIZE_SMALL
9920
            ),
9921
            $url.'&action=delete_item'
9922
        );
9923
9924
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9925
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9926
            if (empty($documentData)) {
9927
                // Try with iid
9928
                $table = Database::get_course_table(TABLE_DOCUMENT);
9929
                $sql = "SELECT path FROM $table
9930
                        WHERE
9931
                              c_id = ".api_get_course_int_id()." AND
9932
                              iid = ".$row['path']." AND
9933
                              path NOT LIKE '%_DELETED_%'";
9934
                $result = Database::query($sql);
9935
                $documentData = Database::fetch_array($result);
9936
                if ($documentData) {
9937
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9938
                }
9939
            }
9940
            if (isset($documentData['absolute_path_from_document'])) {
9941
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9942
            }
9943
        }
9944
9945
        $return .= '</div>';
9946
9947
        if (!empty($audio_player)) {
9948
            $return .= $audio_player;
9949
        }
9950
9951
        return $return;
9952
    }
9953
9954
    /**
9955
     * Creates the javascript needed for filling up the checkboxes without page reload.
9956
     *
9957
     * @return string
9958
     */
9959
    public function get_js_dropdown_array()
9960
    {
9961
        $course_id = api_get_course_int_id();
9962
        $return = 'var child_name = new Array();'."\n";
9963
        $return .= 'var child_value = new Array();'."\n\n";
9964
        $return .= 'child_name[0] = new Array();'."\n";
9965
        $return .= 'child_value[0] = new Array();'."\n\n";
9966
9967
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9968
        $i = 0;
9969
        $list = $this->getItemsForForm(true);
9970
9971
        foreach ($list as $row_zero) {
9972
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9973
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9974
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9975
                }
9976
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9977
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9978
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9979
            }
9980
        }
9981
9982
        $return .= "\n";
9983
        $sql = "SELECT * FROM $tbl_lp_item
9984
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9985
        $res = Database::query($sql);
9986
        while ($row = Database::fetch_array($res)) {
9987
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9988
                           WHERE
9989
                                c_id = ".$course_id." AND
9990
                                parent_item_id = ".$row['iid']."
9991
                           ORDER BY display_order ASC";
9992
            $res_parent = Database::query($sql_parent);
9993
            $i = 0;
9994
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9995
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9996
9997
            while ($row_parent = Database::fetch_array($res_parent)) {
9998
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9999
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
10000
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
10001
            }
10002
            $return .= "\n";
10003
        }
10004
10005
        $return .= "
10006
            function load_cbo(id) {
10007
                if (!id) {
10008
                    return false;
10009
                }
10010
10011
                var cbo = document.getElementById('previous');
10012
                for (var i = cbo.length - 1; i > 0; i--) {
10013
                    cbo.options[i] = null;
10014
                }
10015
10016
                var k=0;
10017
                for(var i = 1; i <= child_name[id].length; i++){
10018
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
10019
                    option.style.paddingLeft = '40px';
10020
                    cbo.options[i] = option;
10021
                    k = i;
10022
                }
10023
10024
                cbo.options[k].selected = true;
10025
                $('#previous').selectpicker('refresh');
10026
            }";
10027
10028
        return $return;
10029
    }
10030
10031
    /**
10032
     * Display the form to allow moving an item.
10033
     *
10034
     * @param learnpathItem $item Item ID
10035
     *
10036
     * @throws Exception
10037
     * @throws HTML_QuickForm_Error
10038
     *
10039
     * @return string HTML form
10040
     */
10041
    public function display_move_item($item)
10042
    {
10043
        $return = '';
10044
        if ($item) {
10045
            $item_id = $item->getIid();
10046
            $type = $item->get_type();
10047
            $path = (int) $item->get_path();
10048
10049
            switch ($type) {
10050
                case 'dir':
10051
                case 'asset':
10052
                    $return .= $this->display_manipulate($item_id, $type);
10053
                    $return .= $this->display_item_form(
10054
                        $type,
10055
                        get_lang('MoveCurrentChapter'),
10056
                        'move',
10057
                        $item_id,
10058
                        $item
10059
                    );
10060
                    break;
10061
                case TOOL_DOCUMENT:
10062
                    $return .= $this->display_manipulate($item_id, $type);
10063
                    $return .= $this->display_document_form('move', $item_id, null, $item);
10064
                    break;
10065
                case TOOL_LINK:
10066
                    $return .= $this->display_manipulate($item_id, $type);
10067
                    $return .= $this->display_link_form('move', $item_id, $path, $item);
10068
                    break;
10069
                case TOOL_HOTPOTATOES:
10070
                    $return .= $this->display_manipulate($item_id, $type);
10071
                    $return .= $this->display_link_form('move', $item_id, $item);
10072
                    break;
10073
                case TOOL_QUIZ:
10074
                    $return .= $this->display_manipulate($item_id, $type);
10075
                    $return .= $this->display_quiz_form('move', $item_id, $item);
10076
                    break;
10077
                case TOOL_STUDENTPUBLICATION:
10078
                    $return .= $this->display_manipulate($item_id, $type);
10079
                    $return .= $this->display_student_publication_form('move', $item_id, $path, $item);
10080
                    break;
10081
                case TOOL_FORUM:
10082
                    $return .= $this->display_manipulate($item_id, $type);
10083
                    $return .= $this->display_forum_form('move', $item_id, $path);
10084
                    break;
10085
                case TOOL_THREAD:
10086
                    $return .= $this->display_manipulate($item_id, $type);
10087
                    $return .= $this->display_forum_form('move', $item_id, $path);
10088
                    break;
10089
            }
10090
        }
10091
10092
        return $return;
10093
    }
10094
10095
    /**
10096
     * Return HTML form to allow prerequisites selection.
10097
     *
10098
     * @todo use FormValidator
10099
     *
10100
     * @param int Item ID
10101
     *
10102
     * @return string HTML form
10103
     */
10104
    public function display_item_prerequisites_form($item_id = 0)
10105
    {
10106
        $course_id = api_get_course_int_id();
10107
        $item_id = (int) $item_id;
10108
10109
        if (empty($item_id)) {
10110
            return '';
10111
        }
10112
10113
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
10114
10115
        /* Current prerequisite */
10116
        $sql = "SELECT * FROM $tbl_lp_item
10117
                WHERE iid = $item_id";
10118
        $result = Database::query($sql);
10119
        $row = Database::fetch_array($result);
10120
        $prerequisiteId = $row['prerequisite'];
10121
10122
        $return = '<legend>';
10123
        $return .= get_lang('AddEditPrerequisites');
10124
        $return .= '</legend>';
10125
        $return .= '<form method="POST">';
10126
        $return .= '<div class="table-responsive">';
10127
        $return .= '<table class="table table-hover">';
10128
        $return .= '<thead>';
10129
        $return .= '<tr>';
10130
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
10131
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
10132
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
10133
        $return .= '</tr>';
10134
        $return .= '</thead>';
10135
10136
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
10137
        $return .= '<tbody>';
10138
        $return .= '<tr>';
10139
        $return .= '<td colspan="3">';
10140
        $return .= '<div class="radio learnpath"><label for="idNone">';
10141
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
10142
        $return .= get_lang('None').'</label>';
10143
        $return .= '</div>';
10144
        $return .= '</tr>';
10145
10146
        $sql = "SELECT * FROM $tbl_lp_item
10147
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
10148
        $result = Database::query($sql);
10149
10150
        $selectedMinScore = [];
10151
        $selectedMaxScore = [];
10152
        $masteryScore = [];
10153
        while ($row = Database::fetch_array($result)) {
10154
            if ($row['iid'] == $item_id) {
10155
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
10156
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
10157
            }
10158
            $masteryScore[$row['iid']] = $row['mastery_score'];
10159
        }
10160
10161
        $arrLP = $this->getItemsForForm();
10162
        $this->tree_array($arrLP);
10163
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
10164
        unset($this->arrMenu);
10165
10166
        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...
10167
            $item = $arrLP[$i];
10168
10169
            if ($item['id'] == $item_id) {
10170
                break;
10171
            }
10172
10173
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
10174
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10175
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10176
10177
            $return .= '<tr>';
10178
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10179
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10180
            $return .= '<label for="id'.$item['id'].'">';
10181
10182
            $checked = '';
10183
            if (null !== $prerequisiteId) {
10184
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10185
            }
10186
10187
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10188
10189
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10190
10191
            $icon_name = str_replace(' ', '', $item['item_type']);
10192
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10193
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10194
            } else {
10195
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10196
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10197
                } else {
10198
                    $return .= Display::return_icon('folder_document.png');
10199
                }
10200
            }
10201
10202
            $return .= $item['title'].'</label>';
10203
            $return .= '</div>';
10204
            $return .= '</td>';
10205
10206
            if ($item['item_type'] == TOOL_QUIZ) {
10207
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10208
                $lpItemObj = new LpItem($course_id, $item['id']);
10209
                $exercise = new Exercise($course_id);
10210
                $exercise->read($lpItemObj->path);
10211
                $lpItemObj->max_score = $exercise->get_max_score();
10212
                $lpItemObj->update();
10213
                $item['max_score'] = $lpItemObj->max_score;
10214
10215
                //if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10216
                if (!isset($selectedMinScore[$item['id']]) && !empty($masteryScoreAsMinValue)) {
10217
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10218
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10219
                }
10220
10221
                $return .= '<td>';
10222
                $return .= '<input
10223
                    class="form-control"
10224
                    size="4" maxlength="3"
10225
                    name="min_'.$item['id'].'"
10226
                    type="number"
10227
                    min="0"
10228
                    step="any"
10229
                    max="'.$item['max_score'].'"
10230
                    value="'.$selectedMinScoreValue.'"
10231
                />';
10232
                $return .= '</td>';
10233
                $return .= '<td>';
10234
                $return .= '<input
10235
                    class="form-control"
10236
                    size="4"
10237
                    maxlength="3"
10238
                    name="max_'.$item['id'].'"
10239
                    type="number"
10240
                    min="0"
10241
                    step="any"
10242
                    max="'.$item['max_score'].'"
10243
                    value="'.$selectedMaxScoreValue.'"
10244
                />';
10245
                $return .= '</td>';
10246
            }
10247
10248
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10249
                $return .= '<td>';
10250
                $return .= '<input
10251
                    size="4"
10252
                    maxlength="3"
10253
                    name="min_'.$item['id'].'"
10254
                    type="number"
10255
                    min="0"
10256
                    step="any"
10257
                    max="'.$item['max_score'].'"
10258
                    value="'.$selectedMinScoreValue.'"
10259
                />';
10260
                $return .= '</td>';
10261
                $return .= '<td>';
10262
                $return .= '<input
10263
                    size="4"
10264
                    maxlength="3"
10265
                    name="max_'.$item['id'].'"
10266
                    type="number"
10267
                    min="0"
10268
                    step="any"
10269
                    max="'.$item['max_score'].'"
10270
                    value="'.$selectedMaxScoreValue.'"
10271
                />';
10272
                $return .= '</td>';
10273
            }
10274
            $return .= '</tr>';
10275
        }
10276
        $return .= '<tr>';
10277
        $return .= '</tr>';
10278
        $return .= '</tbody>';
10279
        $return .= '</table>';
10280
        $return .= '</div>';
10281
        $return .= '<div class="form-group">';
10282
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10283
            get_lang('ModifyPrerequisites').'</button>';
10284
        $return .= '</form>';
10285
10286
        return $return;
10287
    }
10288
10289
    /**
10290
     * Return HTML list to allow prerequisites selection for lp.
10291
     *
10292
     * @return string HTML form
10293
     */
10294
    public function display_lp_prerequisites_list()
10295
    {
10296
        $course_id = api_get_course_int_id();
10297
        $lp_id = $this->lp_id;
10298
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10299
10300
        // get current prerequisite
10301
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10302
        $result = Database::query($sql);
10303
        $row = Database::fetch_array($result);
10304
        $prerequisiteId = $row['prerequisite'];
10305
        $session_id = api_get_session_id();
10306
        $session_condition = api_get_session_condition($session_id, true, true);
10307
        $sql = "SELECT * FROM $tbl_lp
10308
                WHERE c_id = $course_id $session_condition
10309
                ORDER BY display_order ";
10310
        $rs = Database::query($sql);
10311
        $return = '';
10312
        $return .= '<select name="prerequisites" class="form-control">';
10313
        $return .= '<option value="0">'.get_lang('None').'</option>';
10314
        if (Database::num_rows($rs) > 0) {
10315
            while ($row = Database::fetch_array($rs)) {
10316
                if ($row['id'] == $lp_id) {
10317
                    continue;
10318
                }
10319
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10320
            }
10321
        }
10322
        $return .= '</select>';
10323
10324
        return $return;
10325
    }
10326
10327
    /**
10328
     * Creates a list with all the documents in it.
10329
     *
10330
     * @param bool $showInvisibleFiles
10331
     *
10332
     * @throws Exception
10333
     * @throws HTML_QuickForm_Error
10334
     *
10335
     * @return string
10336
     */
10337
    public function get_documents($showInvisibleFiles = false)
10338
    {
10339
        $course_info = api_get_course_info();
10340
        $sessionId = api_get_session_id();
10341
        $documentTree = DocumentManager::get_document_preview(
10342
            $course_info,
10343
            $this->lp_id,
10344
            null,
10345
            $sessionId,
10346
            true,
10347
            null,
10348
            null,
10349
            $showInvisibleFiles,
10350
            true
10351
        );
10352
10353
        $headers = [
10354
            get_lang('Files'),
10355
            get_lang('CreateTheDocument'),
10356
            get_lang('CreateReadOutText'),
10357
            get_lang('Upload'),
10358
        ];
10359
10360
        $form = new FormValidator(
10361
            'form_upload',
10362
            'POST',
10363
            $this->getCurrentBuildingModeURL(),
10364
            '',
10365
            ['enctype' => 'multipart/form-data']
10366
        );
10367
10368
        $folders = DocumentManager::get_all_document_folders(
10369
            api_get_course_info(),
10370
            0,
10371
            true
10372
        );
10373
10374
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10375
10376
        DocumentManager::build_directory_selector(
10377
            $folders,
10378
            $lpPathInfo['id'],
10379
            [],
10380
            true,
10381
            $form,
10382
            'directory_parent_id'
10383
        );
10384
10385
        $group = [
10386
            $form->createElement(
10387
                'radio',
10388
                'if_exists',
10389
                get_lang('UplWhatIfFileExists'),
10390
                get_lang('UplDoNothing'),
10391
                'nothing'
10392
            ),
10393
            $form->createElement(
10394
                'radio',
10395
                'if_exists',
10396
                null,
10397
                get_lang('UplOverwriteLong'),
10398
                'overwrite'
10399
            ),
10400
            $form->createElement(
10401
                'radio',
10402
                'if_exists',
10403
                null,
10404
                get_lang('UplRenameLong'),
10405
                'rename'
10406
            ),
10407
        ];
10408
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10409
10410
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10411
        $defaultFileExistsOption = 'rename';
10412
        if (!empty($fileExistsOption)) {
10413
            $defaultFileExistsOption = $fileExistsOption;
10414
        }
10415
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10416
10417
        // Check box options
10418
        $form->addElement(
10419
            'checkbox',
10420
            'unzip',
10421
            get_lang('Options'),
10422
            get_lang('Uncompress')
10423
        );
10424
10425
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10426
        $form->addMultipleUpload($url);
10427
        $new = $this->display_document_form('add', 0);
10428
        $frmReadOutText = $this->displayFrmReadOutText('add');
10429
        $tabs = Display::tabs(
10430
            $headers,
10431
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10432
            'subtab'
10433
        );
10434
10435
        return $tabs;
10436
    }
10437
10438
    /**
10439
     * Creates a list with all the exercises (quiz) in it.
10440
     *
10441
     * @return string
10442
     */
10443
    public function get_exercises()
10444
    {
10445
        $course_id = api_get_course_int_id();
10446
        $session_id = api_get_session_id();
10447
        $userInfo = api_get_user_info();
10448
10449
        // New for hotpotatoes.
10450
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10451
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10452
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10453
        $condition_session = api_get_session_condition($session_id, true, true);
10454
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10455
10456
        $activeCondition = ' active <> -1 ';
10457
        if ($setting) {
10458
            $activeCondition = ' active = 1 ';
10459
        }
10460
10461
        $categoryCondition = '';
10462
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10463
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10464
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10465
        }
10466
10467
        $keywordCondition = '';
10468
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10469
10470
        if (!empty($keyword)) {
10471
            $keyword = Database::escape_string($keyword);
10472
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10473
        }
10474
10475
        $sql_quiz = "SELECT * FROM $tbl_quiz
10476
                     WHERE
10477
                            c_id = $course_id AND
10478
                            $activeCondition
10479
                            $condition_session
10480
                            $categoryCondition
10481
                            $keywordCondition
10482
                     ORDER BY title ASC";
10483
10484
        $sql_hot = "SELECT * FROM $tbl_doc
10485
                    WHERE
10486
                        c_id = $course_id AND
10487
                        path LIKE '".$uploadPath."/%/%htm%'
10488
                        $condition_session
10489
                     ORDER BY id ASC";
10490
10491
        $res_quiz = Database::query($sql_quiz);
10492
        $res_hot = Database::query($sql_hot);
10493
10494
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10495
10496
        // Create a search-box
10497
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10498
        $form->addHidden('action', 'add_item');
10499
        $form->addHidden('type', 'step');
10500
        $form->addHidden('lp_id', $this->lp_id);
10501
        $form->addHidden('lp_build_selected', '2');
10502
10503
        $form->addCourseHiddenParams();
10504
        $form->addText(
10505
            'keyword',
10506
            get_lang('Search'),
10507
            false,
10508
            [
10509
                'aria-label' => get_lang('Search'),
10510
            ]
10511
        );
10512
10513
        if (api_get_configuration_value('allow_exercise_categories')) {
10514
            $manager = new ExerciseCategoryManager();
10515
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10516
            if (!empty($options)) {
10517
                $form->addSelect(
10518
                    'category_id',
10519
                    get_lang('Category'),
10520
                    $options,
10521
                    ['placeholder' => get_lang('SelectAnOption')]
10522
                );
10523
            }
10524
        }
10525
10526
        $form->addButtonSearch(get_lang('Search'));
10527
        $return = $form->returnForm();
10528
10529
        $return .= '<ul class="lp_resource">';
10530
10531
        $return .= '<li class="lp_resource_element">';
10532
        $return .= Display::return_icon('new_exercice.png');
10533
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10534
            get_lang('NewExercise').'</a>';
10535
        $return .= '</li>';
10536
10537
        $previewIcon = Display::return_icon(
10538
            'preview_view.png',
10539
            get_lang('Preview')
10540
        );
10541
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10542
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10543
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10544
10545
        // Display hotpotatoes
10546
        while ($row_hot = Database::fetch_array($res_hot)) {
10547
            $link = Display::url(
10548
                $previewIcon,
10549
                $exerciseUrl.'&file='.$row_hot['path'],
10550
                ['target' => '_blank']
10551
            );
10552
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10553
            $return .= '<a class="moved" href="#">';
10554
            $return .= Display::return_icon(
10555
                'move_everywhere.png',
10556
                get_lang('Move'),
10557
                [],
10558
                ICON_SIZE_TINY
10559
            );
10560
            $return .= '</a> ';
10561
            $return .= Display::return_icon('hotpotatoes_s.png');
10562
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10563
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10564
            $return .= '</li>';
10565
        }
10566
10567
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10568
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10569
            $title = strip_tags(
10570
                api_html_entity_decode($row_quiz['title'])
10571
            );
10572
10573
            $visibility = api_get_item_visibility(
10574
                ['real_id' => $course_id],
10575
                TOOL_QUIZ,
10576
                $row_quiz['iid'],
10577
                $session_id
10578
            );
10579
10580
            $link = Display::url(
10581
                $previewIcon,
10582
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10583
                ['target' => '_blank']
10584
            );
10585
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10586
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10587
            $return .= $quizIcon;
10588
            $sessionStar = api_get_session_image(
10589
                $row_quiz['session_id'],
10590
                $userInfo['status']
10591
            );
10592
            $return .= Display::url(
10593
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10594
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10595
                [
10596
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10597
                ]
10598
            );
10599
            $return .= '</li>';
10600
        }
10601
10602
        $return .= '</ul>';
10603
10604
        return $return;
10605
    }
10606
10607
    /**
10608
     * Creates a list with all the links in it.
10609
     *
10610
     * @return string
10611
     */
10612
    public function get_links()
10613
    {
10614
        $selfUrl = api_get_self();
10615
        $courseIdReq = api_get_cidreq();
10616
        $course = api_get_course_info();
10617
        $userInfo = api_get_user_info();
10618
10619
        $course_id = $course['real_id'];
10620
        $tbl_link = Database::get_course_table(TABLE_LINK);
10621
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10622
        $moveEverywhereIcon = Display::return_icon(
10623
            'move_everywhere.png',
10624
            get_lang('Move'),
10625
            [],
10626
            ICON_SIZE_TINY
10627
        );
10628
10629
        $session_id = api_get_session_id();
10630
        $condition_session = api_get_session_condition(
10631
            $session_id,
10632
            true,
10633
            true,
10634
            'link.session_id'
10635
        );
10636
10637
        $sql = "SELECT
10638
                    link.id as link_id,
10639
                    link.title as link_title,
10640
                    link.session_id as link_session_id,
10641
                    link.category_id as category_id,
10642
                    link_category.category_title as category_title
10643
                FROM $tbl_link as link
10644
                LEFT JOIN $linkCategoryTable as link_category
10645
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10646
                WHERE link.c_id = $course_id $condition_session
10647
                ORDER BY link_category.category_title ASC, link.title ASC";
10648
        $result = Database::query($sql);
10649
        $categorizedLinks = [];
10650
        $categories = [];
10651
10652
        while ($link = Database::fetch_array($result)) {
10653
            if (!$link['category_id']) {
10654
                $link['category_title'] = get_lang('Uncategorized');
10655
            }
10656
            $categories[$link['category_id']] = $link['category_title'];
10657
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10658
        }
10659
10660
        $linksHtmlCode =
10661
            '<script>
10662
            function toggle_tool(tool, id) {
10663
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10664
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10665
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10666
                } else {
10667
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10668
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10669
                }
10670
            }
10671
        </script>
10672
10673
        <ul class="lp_resource">
10674
            <li class="lp_resource_element">
10675
                '.Display::return_icon('linksnew.gif').'
10676
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10677
                get_lang('LinkAdd').'
10678
                </a>
10679
            </li>';
10680
10681
        foreach ($categorizedLinks as $categoryId => $links) {
10682
            $linkNodes = null;
10683
            foreach ($links as $key => $linkInfo) {
10684
                $title = $linkInfo['link_title'];
10685
                $linkSessionId = $linkInfo['link_session_id'];
10686
10687
                $link = Display::url(
10688
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10689
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10690
                    ['target' => '_blank']
10691
                );
10692
10693
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10694
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10695
                    $linkNodes .=
10696
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10697
                        <a class="moved" href="#">'.
10698
                            $moveEverywhereIcon.
10699
                        '</a>
10700
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10701
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10702
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10703
                        Security::remove_XSS($title).$sessionStar.$link.
10704
                        '</a>
10705
                    </li>';
10706
                }
10707
            }
10708
            $linksHtmlCode .=
10709
                '<li>
10710
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10711
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10712
                    align="absbottom" />
10713
                </a>
10714
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10715
            </li>
10716
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10717
        }
10718
        $linksHtmlCode .= '</ul>';
10719
10720
        return $linksHtmlCode;
10721
    }
10722
10723
    /**
10724
     * Creates a list with all the student publications in it.
10725
     *
10726
     * @return string
10727
     */
10728
    public function get_student_publications()
10729
    {
10730
        $return = '<ul class="lp_resource">';
10731
        $return .= '<li class="lp_resource_element">';
10732
        $return .= Display::return_icon('works_new.gif');
10733
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10734
            get_lang('AddAssignmentPage').'</a>';
10735
        $return .= '</li>';
10736
10737
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10738
        $works = getWorkListTeacher(0, 100, null, null, null);
10739
        if (!empty($works)) {
10740
            foreach ($works as $work) {
10741
                $link = Display::url(
10742
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10743
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10744
                    ['target' => '_blank']
10745
                );
10746
10747
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10748
                $return .= '<a class="moved" href="#">';
10749
                $return .= Display::return_icon(
10750
                    'move_everywhere.png',
10751
                    get_lang('Move'),
10752
                    [],
10753
                    ICON_SIZE_TINY
10754
                );
10755
                $return .= '</a> ';
10756
10757
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10758
                $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.'">'.
10759
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10760
                </a>';
10761
10762
                $return .= '</li>';
10763
            }
10764
        }
10765
10766
        $return .= '</ul>';
10767
10768
        return $return;
10769
    }
10770
10771
    /**
10772
     * Creates a list with all the forums in it.
10773
     *
10774
     * @return string
10775
     */
10776
    public function get_forums()
10777
    {
10778
        require_once '../forum/forumfunction.inc.php';
10779
10780
        $forumCategories = get_forum_categories();
10781
        $forumsInNoCategory = get_forums_in_category(0);
10782
        if (!empty($forumsInNoCategory)) {
10783
            $forumCategories = array_merge(
10784
                $forumCategories,
10785
                [
10786
                    [
10787
                        'cat_id' => 0,
10788
                        'session_id' => 0,
10789
                        'visibility' => 1,
10790
                        'cat_comment' => null,
10791
                    ],
10792
                ]
10793
            );
10794
        }
10795
10796
        $forumList = get_forums();
10797
        $a_forums = [];
10798
        foreach ($forumCategories as $forumCategory) {
10799
            // The forums in this category.
10800
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10801
            if (!empty($forumsInCategory)) {
10802
                foreach ($forumList as $forum) {
10803
                    if (isset($forum['forum_category']) &&
10804
                        $forum['forum_category'] == $forumCategory['cat_id']
10805
                    ) {
10806
                        $a_forums[] = $forum;
10807
                    }
10808
                }
10809
            }
10810
        }
10811
10812
        $return = '<ul class="lp_resource">';
10813
10814
        // First add link
10815
        $return .= '<li class="lp_resource_element">';
10816
        $return .= Display::return_icon('new_forum.png');
10817
        $return .= Display::url(
10818
            get_lang('CreateANewForum'),
10819
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10820
                'action' => 'add',
10821
                'content' => 'forum',
10822
                'lp_id' => $this->lp_id,
10823
            ]),
10824
            ['title' => get_lang('CreateANewForum')]
10825
        );
10826
        $return .= '</li>';
10827
10828
        $return .= '<script>
10829
            function toggle_forum(forum_id) {
10830
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10831
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10832
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10833
                } else {
10834
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10835
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10836
                }
10837
            }
10838
        </script>';
10839
10840
        foreach ($a_forums as $forum) {
10841
            if (!empty($forum['forum_id'])) {
10842
                $link = Display::url(
10843
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10844
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10845
                    ['target' => '_blank']
10846
                );
10847
10848
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10849
                $return .= '<a class="moved" href="#">';
10850
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10851
                $return .= ' </a>';
10852
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10853
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10854
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10855
                            </a>
10856
                            <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">'.
10857
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10858
10859
                $return .= '</li>';
10860
10861
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10862
                $a_threads = get_threads($forum['forum_id']);
10863
                if (is_array($a_threads)) {
10864
                    foreach ($a_threads as $thread) {
10865
                        $link = Display::url(
10866
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10867
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10868
                            ['target' => '_blank']
10869
                        );
10870
10871
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10872
                        $return .= '&nbsp;<a class="moved" href="#">';
10873
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10874
                        $return .= ' </a>';
10875
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10876
                        $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.'">'.
10877
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10878
                        $return .= '</li>';
10879
                    }
10880
                }
10881
                $return .= '</div>';
10882
            }
10883
        }
10884
        $return .= '</ul>';
10885
10886
        return $return;
10887
    }
10888
10889
    /**
10890
     * // TODO: The output encoding should be equal to the system encoding.
10891
     *
10892
     * Exports the learning path as a SCORM package. This is the main function that
10893
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10894
     * whole thing and returns the zip.
10895
     *
10896
     * This method needs to be called in PHP5, as it will fail with non-adequate
10897
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10898
     * you need to call it on a learnpath object.
10899
     *
10900
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10901
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10902
     * path has been modified, it should use the generic method here below.
10903
     *
10904
     * @return string Returns the zip package string, or null if error
10905
     */
10906
    public function scormExport()
10907
    {
10908
        api_set_more_memory_and_time_limits();
10909
10910
        $_course = api_get_course_info();
10911
        $course_id = $_course['real_id'];
10912
        // Create the zip handler (this will remain available throughout the method).
10913
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10914
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10915
        $temp_dir_short = uniqid('scorm_export', true);
10916
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10917
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10918
        $zip_folder = new PclZip($temp_zip_file);
10919
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10920
        $root_path = $main_path = api_get_path(SYS_PATH);
10921
        $files_cleanup = [];
10922
10923
        // Place to temporarily stash the zip file.
10924
        // create the temp dir if it doesn't exist
10925
        // or do a cleanup before creating the zip file.
10926
        if (!is_dir($temp_zip_dir)) {
10927
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10928
        } else {
10929
            // Cleanup: Check the temp dir for old files and delete them.
10930
            $handle = opendir($temp_zip_dir);
10931
            while (false !== ($file = readdir($handle))) {
10932
                if ($file != '.' && $file != '..') {
10933
                    unlink("$temp_zip_dir/$file");
10934
                }
10935
            }
10936
            closedir($handle);
10937
        }
10938
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10939
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10940
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10941
        ) {
10942
            // Remove the possible . at the end of the path.
10943
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10944
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10945
            mkdir(
10946
                $dest_path_to_scorm_folder,
10947
                api_get_permissions_for_new_directories(),
10948
                true
10949
            );
10950
            copyr(
10951
                $current_course_path.'/scorm/'.$this->path,
10952
                $dest_path_to_scorm_folder,
10953
                ['imsmanifest'],
10954
                $zip_files
10955
            );
10956
        }
10957
10958
        // Build a dummy imsmanifest structure.
10959
        // Do not add to the zip yet (we still need it).
10960
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10961
        // Aggregation Model official document, section "2.3 Content Packaging".
10962
        // We are going to build a UTF-8 encoded manifest.
10963
        // Later we will recode it to the desired (and supported) encoding.
10964
        $xmldoc = new DOMDocument('1.0');
10965
        $root = $xmldoc->createElement('manifest');
10966
        $root->setAttribute('identifier', 'SingleCourseManifest');
10967
        $root->setAttribute('version', '1.1');
10968
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10969
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10970
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10971
        $root->setAttribute(
10972
            'xsi:schemaLocation',
10973
            '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'
10974
        );
10975
        // Build mandatory sub-root container elements.
10976
        $metadata = $xmldoc->createElement('metadata');
10977
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10978
        $metadata->appendChild($md_schema);
10979
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10980
        $metadata->appendChild($md_schemaversion);
10981
        $root->appendChild($metadata);
10982
10983
        $organizations = $xmldoc->createElement('organizations');
10984
        $resources = $xmldoc->createElement('resources');
10985
10986
        // Build the only organization we will use in building our learnpaths.
10987
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10988
        $organization = $xmldoc->createElement('organization');
10989
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10990
        // To set the title of the SCORM entity (=organization), we take the name given
10991
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10992
        // learning path charset) as it is the encoding that defines how it is stored
10993
        // in the database. Then we convert it to HTML entities again as the "&" character
10994
        // alone is not authorized in XML (must be &amp;).
10995
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10996
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10997
        $organization->appendChild($org_title);
10998
        $folder_name = 'document';
10999
11000
        // Removes the learning_path/scorm_folder path when exporting see #4841
11001
        $path_to_remove = '';
11002
        $path_to_replace = '';
11003
        $result = $this->generate_lp_folder($_course);
11004
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
11005
            $path_to_remove = 'document'.$result['dir'];
11006
            $path_to_replace = $folder_name.'/';
11007
        }
11008
11009
        // Fixes chamilo scorm exports
11010
        if ($this->ref === 'chamilo_scorm_export') {
11011
            $path_to_remove = 'scorm/'.$this->path.'/document/';
11012
        }
11013
11014
        // For each element, add it to the imsmanifest structure, then add it to the zip.
11015
        $link_updates = [];
11016
        $links_to_create = [];
11017
        foreach ($this->ordered_items as $index => $itemId) {
11018
            /** @var learnpathItem $item */
11019
            $item = $this->items[$itemId];
11020
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
11021
                // Get included documents from this item.
11022
                if ($item->type === 'sco') {
11023
                    $inc_docs = $item->get_resources_from_source(
11024
                        null,
11025
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
11026
                    );
11027
                } else {
11028
                    $inc_docs = $item->get_resources_from_source();
11029
                }
11030
11031
                // Give a child element <item> to the <organization> element.
11032
                $my_item_id = $item->get_id();
11033
                $my_item = $xmldoc->createElement('item');
11034
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
11035
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
11036
                $my_item->setAttribute('isvisible', 'true');
11037
                // Give a child element <title> to the <item> element.
11038
                $my_title = $xmldoc->createElement(
11039
                    'title',
11040
                    htmlspecialchars(
11041
                        api_utf8_encode($item->get_title()),
11042
                        ENT_QUOTES,
11043
                        'UTF-8'
11044
                    )
11045
                );
11046
                $my_item->appendChild($my_title);
11047
                // Give a child element <adlcp:prerequisites> to the <item> element.
11048
                $my_prereqs = $xmldoc->createElement(
11049
                    'adlcp:prerequisites',
11050
                    $this->get_scorm_prereq_string($my_item_id)
11051
                );
11052
                $my_prereqs->setAttribute('type', 'aicc_script');
11053
                $my_item->appendChild($my_prereqs);
11054
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11055
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
11056
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11057
                //$xmldoc->createElement('adlcp:timelimitaction','');
11058
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11059
                //$xmldoc->createElement('adlcp:datafromlms','');
11060
                // Give a child element <adlcp:masteryscore> to the <item> element.
11061
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11062
                $my_item->appendChild($my_masteryscore);
11063
11064
                // Attach this item to the organization element or hits parent if there is one.
11065
                if (!empty($item->parent) && $item->parent != 0) {
11066
                    $children = $organization->childNodes;
11067
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11068
                    if (is_object($possible_parent)) {
11069
                        $possible_parent->appendChild($my_item);
11070
                    } else {
11071
                        if ($this->debug > 0) {
11072
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
11073
                        }
11074
                    }
11075
                } else {
11076
                    if ($this->debug > 0) {
11077
                        error_log('No parent');
11078
                    }
11079
                    $organization->appendChild($my_item);
11080
                }
11081
11082
                // Get the path of the file(s) from the course directory root.
11083
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11084
                $my_xml_file_path = $my_file_path;
11085
                if (!empty($path_to_remove)) {
11086
                    // From docs
11087
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
11088
11089
                    // From quiz
11090
                    if ($this->ref === 'chamilo_scorm_export') {
11091
                        $path_to_remove = 'scorm/'.$this->path.'/';
11092
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
11093
                    }
11094
                }
11095
11096
                $my_sub_dir = dirname($my_file_path);
11097
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11098
                $my_xml_sub_dir = $my_sub_dir;
11099
                // Give a <resource> child to the <resources> element
11100
                $my_resource = $xmldoc->createElement('resource');
11101
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11102
                $my_resource->setAttribute('type', 'webcontent');
11103
                $my_resource->setAttribute('href', $my_xml_file_path);
11104
                // adlcp:scormtype can be either 'sco' or 'asset'.
11105
                if ($item->type === 'sco') {
11106
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
11107
                } else {
11108
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
11109
                }
11110
                // xml:base is the base directory to find the files declared in this resource.
11111
                $my_resource->setAttribute('xml:base', '');
11112
                // Give a <file> child to the <resource> element.
11113
                $my_file = $xmldoc->createElement('file');
11114
                $my_file->setAttribute('href', $my_xml_file_path);
11115
                $my_resource->appendChild($my_file);
11116
11117
                // Dependency to other files - not yet supported.
11118
                $i = 1;
11119
                if ($inc_docs) {
11120
                    foreach ($inc_docs as $doc_info) {
11121
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
11122
                            continue;
11123
                        }
11124
                        $my_dep = $xmldoc->createElement('resource');
11125
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11126
                        $my_dep->setAttribute('identifier', $res_id);
11127
                        $my_dep->setAttribute('type', 'webcontent');
11128
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
11129
                        $my_dep_file = $xmldoc->createElement('file');
11130
                        // Check type of URL.
11131
                        if ($doc_info[1] == 'remote') {
11132
                            // Remote file. Save url as is.
11133
                            $my_dep_file->setAttribute('href', $doc_info[0]);
11134
                            $my_dep->setAttribute('xml:base', '');
11135
                        } elseif ($doc_info[1] === 'local') {
11136
                            switch ($doc_info[2]) {
11137
                                case 'url':
11138
                                    // Local URL - save path as url for now, don't zip file.
11139
                                    $abs_path = api_get_path(SYS_PATH).
11140
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11141
                                    $current_dir = dirname($abs_path);
11142
                                    $current_dir = str_replace('\\', '/', $current_dir);
11143
                                    $file_path = realpath($abs_path);
11144
                                    $file_path = str_replace('\\', '/', $file_path);
11145
                                    $my_dep_file->setAttribute('href', $file_path);
11146
                                    $my_dep->setAttribute('xml:base', '');
11147
                                    if (strstr($file_path, $main_path) !== false) {
11148
                                        // The calculated real path is really inside Chamilo's root path.
11149
                                        // Reduce file path to what's under the DocumentRoot.
11150
                                        $replace = $file_path;
11151
                                        $file_path = substr($file_path, strlen($root_path) - 1);
11152
                                        $destinationFile = $file_path;
11153
11154
                                        if (strstr($file_path, 'upload/users') !== false) {
11155
                                            $pos = strpos($file_path, 'my_files/');
11156
                                            if ($pos !== false) {
11157
                                                $onlyDirectory = str_replace(
11158
                                                    'upload/users/',
11159
                                                    '',
11160
                                                    substr($file_path, $pos, strlen($file_path))
11161
                                                );
11162
                                            }
11163
                                            $replace = $onlyDirectory;
11164
                                            $destinationFile = $replace;
11165
                                        }
11166
                                        $zip_files_abs[] = $file_path;
11167
                                        $link_updates[$my_file_path][] = [
11168
                                            'orig' => $doc_info[0],
11169
                                            'dest' => $destinationFile,
11170
                                            'replace' => $replace,
11171
                                        ];
11172
                                        $my_dep_file->setAttribute('href', $file_path);
11173
                                        $my_dep->setAttribute('xml:base', '');
11174
                                    } elseif (empty($file_path)) {
11175
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11176
                                        $file_path = str_replace('//', '/', $file_path);
11177
                                        if (file_exists($file_path)) {
11178
                                            // We get the relative path.
11179
                                            $file_path = substr($file_path, strlen($current_dir));
11180
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11181
                                            $link_updates[$my_file_path][] = [
11182
                                                'orig' => $doc_info[0],
11183
                                                'dest' => $file_path,
11184
                                            ];
11185
                                            $my_dep_file->setAttribute('href', $file_path);
11186
                                            $my_dep->setAttribute('xml:base', '');
11187
                                        }
11188
                                    }
11189
                                    break;
11190
                                case 'abs':
11191
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11192
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11193
                                    $my_dep->setAttribute('xml:base', '');
11194
11195
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11196
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11197
                                    $abs_img_path_without_subdir = $doc_info[0];
11198
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11199
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11200
                                    if ($pos === 0) {
11201
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11202
                                    }
11203
11204
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11205
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11206
11207
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11208
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11209
                                    // Check if the current document is in that path.
11210
                                    if (strstr($file_path, $cur_path) !== false) {
11211
                                        $destinationFile = substr($file_path, strlen($cur_path));
11212
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11213
11214
                                        $fileToTest = $cur_path.$my_file_path;
11215
                                        if (!empty($path_to_remove)) {
11216
                                            $fileToTest = str_replace(
11217
                                                $path_to_remove.'/',
11218
                                                $path_to_replace,
11219
                                                $cur_path.$my_file_path
11220
                                            );
11221
                                        }
11222
11223
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11224
11225
                                        // Put the current document in the zip (this array is the array
11226
                                        // that will manage documents already in the course folder - relative).
11227
                                        $zip_files[] = $filePathNoCoursePart;
11228
                                        // Update the links to the current document in the
11229
                                        // containing document (make them relative).
11230
                                        $link_updates[$my_file_path][] = [
11231
                                            'orig' => $doc_info[0],
11232
                                            'dest' => $destinationFile,
11233
                                            'replace' => $relative_path,
11234
                                        ];
11235
11236
                                        $my_dep_file->setAttribute('href', $file_path);
11237
                                        $my_dep->setAttribute('xml:base', '');
11238
                                    } elseif (strstr($file_path, $main_path) !== false) {
11239
                                        // The calculated real path is really inside Chamilo's root path.
11240
                                        // Reduce file path to what's under the DocumentRoot.
11241
                                        $file_path = substr($file_path, strlen($root_path));
11242
                                        $zip_files_abs[] = $file_path;
11243
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11244
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11245
                                        $my_dep->setAttribute('xml:base', '');
11246
                                    } elseif (empty($file_path)) {
11247
                                        // Probably this is an image inside "/main" directory
11248
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11249
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11250
11251
                                        if (file_exists($file_path)) {
11252
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11253
                                                // We get the relative path.
11254
                                                $pos = strpos($file_path, 'main/default_course_document/');
11255
                                                if ($pos !== false) {
11256
                                                    $onlyDirectory = str_replace(
11257
                                                        'main/default_course_document/',
11258
                                                        '',
11259
                                                        substr($file_path, $pos, strlen($file_path))
11260
                                                    );
11261
                                                }
11262
11263
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11264
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11265
                                                $zip_files_abs[] = $fileAbs;
11266
                                                $link_updates[$my_file_path][] = [
11267
                                                    'orig' => $doc_info[0],
11268
                                                    'dest' => $destinationFile,
11269
                                                ];
11270
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11271
                                                $my_dep->setAttribute('xml:base', '');
11272
                                            }
11273
                                        }
11274
                                    }
11275
                                    break;
11276
                                case 'rel':
11277
                                    // Path relative to the current document.
11278
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11279
                                    if (substr($doc_info[0], 0, 2) === '..') {
11280
                                        // Relative path going up.
11281
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11282
                                        $current_dir = str_replace('\\', '/', $current_dir);
11283
                                        $file_path = realpath($current_dir.$doc_info[0]);
11284
                                        $file_path = str_replace('\\', '/', $file_path);
11285
                                        if (strstr($file_path, $main_path) !== false) {
11286
                                            // The calculated real path is really inside Chamilo's root path.
11287
                                            // Reduce file path to what's under the DocumentRoot.
11288
                                            $file_path = substr($file_path, strlen($root_path));
11289
                                            $zip_files_abs[] = $file_path;
11290
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11291
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11292
                                            $my_dep->setAttribute('xml:base', '');
11293
                                        }
11294
                                    } else {
11295
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11296
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11297
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11298
                                    }
11299
                                    break;
11300
                                default:
11301
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11302
                                    $my_dep->setAttribute('xml:base', '');
11303
                                    break;
11304
                            }
11305
                        }
11306
                        $my_dep->appendChild($my_dep_file);
11307
                        $resources->appendChild($my_dep);
11308
                        $dependency = $xmldoc->createElement('dependency');
11309
                        $dependency->setAttribute('identifierref', $res_id);
11310
                        $my_resource->appendChild($dependency);
11311
                        $i++;
11312
                    }
11313
                }
11314
                $resources->appendChild($my_resource);
11315
                $zip_files[] = $my_file_path;
11316
            } else {
11317
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11318
                switch ($item->type) {
11319
                    case TOOL_LINK:
11320
                        $my_item = $xmldoc->createElement('item');
11321
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11322
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11323
                        $my_item->setAttribute('isvisible', 'true');
11324
                        // Give a child element <title> to the <item> element.
11325
                        $my_title = $xmldoc->createElement(
11326
                            'title',
11327
                            htmlspecialchars(
11328
                                api_utf8_encode($item->get_title()),
11329
                                ENT_QUOTES,
11330
                                'UTF-8'
11331
                            )
11332
                        );
11333
                        $my_item->appendChild($my_title);
11334
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11335
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11336
                        $my_prereqs->setAttribute('type', 'aicc_script');
11337
                        $my_item->appendChild($my_prereqs);
11338
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11339
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11340
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11341
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11342
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11343
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11344
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11345
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11346
                        $my_item->appendChild($my_masteryscore);
11347
11348
                        // Attach this item to the organization element or its parent if there is one.
11349
                        if (!empty($item->parent) && $item->parent != 0) {
11350
                            $children = $organization->childNodes;
11351
                            for ($i = 0; $i < $children->length; $i++) {
11352
                                $item_temp = $children->item($i);
11353
                                if ($item_temp->nodeName == 'item') {
11354
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11355
                                        $item_temp->appendChild($my_item);
11356
                                    }
11357
                                }
11358
                            }
11359
                        } else {
11360
                            $organization->appendChild($my_item);
11361
                        }
11362
11363
                        $my_file_path = 'link_'.$item->get_id().'.html';
11364
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11365
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11366
                        $rs = Database::query($sql);
11367
                        if ($link = Database::fetch_array($rs)) {
11368
                            $url = $link['url'];
11369
                            $title = stripslashes($link['title']);
11370
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11371
                            $my_xml_file_path = $my_file_path;
11372
                            $my_sub_dir = dirname($my_file_path);
11373
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11374
                            $my_xml_sub_dir = $my_sub_dir;
11375
                            // Give a <resource> child to the <resources> element.
11376
                            $my_resource = $xmldoc->createElement('resource');
11377
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11378
                            $my_resource->setAttribute('type', 'webcontent');
11379
                            $my_resource->setAttribute('href', $my_xml_file_path);
11380
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11381
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11382
                            // xml:base is the base directory to find the files declared in this resource.
11383
                            $my_resource->setAttribute('xml:base', '');
11384
                            // give a <file> child to the <resource> element.
11385
                            $my_file = $xmldoc->createElement('file');
11386
                            $my_file->setAttribute('href', $my_xml_file_path);
11387
                            $my_resource->appendChild($my_file);
11388
                            $resources->appendChild($my_resource);
11389
                        }
11390
                        break;
11391
                    case TOOL_QUIZ:
11392
                        $exe_id = $item->path;
11393
                        // Should be using ref when everything will be cleaned up in this regard.
11394
                        $exe = new Exercise();
11395
                        $exe->read($exe_id);
11396
                        $my_item = $xmldoc->createElement('item');
11397
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11398
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11399
                        $my_item->setAttribute('isvisible', 'true');
11400
                        // Give a child element <title> to the <item> element.
11401
                        $my_title = $xmldoc->createElement(
11402
                            'title',
11403
                            htmlspecialchars(
11404
                                api_utf8_encode($item->get_title()),
11405
                                ENT_QUOTES,
11406
                                'UTF-8'
11407
                            )
11408
                        );
11409
                        $my_item->appendChild($my_title);
11410
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11411
                        $my_item->appendChild($my_max_score);
11412
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11413
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11414
                        $my_prereqs->setAttribute('type', 'aicc_script');
11415
                        $my_item->appendChild($my_prereqs);
11416
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11417
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11418
                        $my_item->appendChild($my_masteryscore);
11419
11420
                        // Attach this item to the organization element or hits parent if there is one.
11421
                        if (!empty($item->parent) && $item->parent != 0) {
11422
                            $children = $organization->childNodes;
11423
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11424
                            if ($possible_parent) {
11425
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11426
                                    $possible_parent->appendChild($my_item);
11427
                                }
11428
                            }
11429
                        } else {
11430
                            $organization->appendChild($my_item);
11431
                        }
11432
11433
                        // Get the path of the file(s) from the course directory root
11434
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11435
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11436
                        // Write the contents of the exported exercise into a (big) html file
11437
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11438
                        $scormExercise = new ScormExercise($exe, true);
11439
                        $contents = $scormExercise->export();
11440
11441
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11442
                        $res = file_put_contents($tmp_file_path, $contents);
11443
                        if ($res === false) {
11444
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11445
                        }
11446
                        $files_cleanup[] = $tmp_file_path;
11447
                        $my_xml_file_path = $my_file_path;
11448
                        $my_sub_dir = dirname($my_file_path);
11449
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11450
                        $my_xml_sub_dir = $my_sub_dir;
11451
                        // Give a <resource> child to the <resources> element.
11452
                        $my_resource = $xmldoc->createElement('resource');
11453
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11454
                        $my_resource->setAttribute('type', 'webcontent');
11455
                        $my_resource->setAttribute('href', $my_xml_file_path);
11456
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11457
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11458
                        // xml:base is the base directory to find the files declared in this resource.
11459
                        $my_resource->setAttribute('xml:base', '');
11460
                        // Give a <file> child to the <resource> element.
11461
                        $my_file = $xmldoc->createElement('file');
11462
                        $my_file->setAttribute('href', $my_xml_file_path);
11463
                        $my_resource->appendChild($my_file);
11464
11465
                        // Get included docs.
11466
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11467
11468
                        // Dependency to other files - not yet supported.
11469
                        $i = 1;
11470
                        foreach ($inc_docs as $doc_info) {
11471
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11472
                                continue;
11473
                            }
11474
                            $my_dep = $xmldoc->createElement('resource');
11475
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11476
                            $my_dep->setAttribute('identifier', $res_id);
11477
                            $my_dep->setAttribute('type', 'webcontent');
11478
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11479
                            $my_dep_file = $xmldoc->createElement('file');
11480
                            // Check type of URL.
11481
                            if ($doc_info[1] == 'remote') {
11482
                                // Remote file. Save url as is.
11483
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11484
                                $my_dep->setAttribute('xml:base', '');
11485
                            } elseif ($doc_info[1] == 'local') {
11486
                                switch ($doc_info[2]) {
11487
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11488
                                        // Save file but as local file (retrieve from URL).
11489
                                        $abs_path = api_get_path(SYS_PATH).
11490
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11491
                                        $current_dir = dirname($abs_path);
11492
                                        $current_dir = str_replace('\\', '/', $current_dir);
11493
                                        $file_path = realpath($abs_path);
11494
                                        $file_path = str_replace('\\', '/', $file_path);
11495
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11496
                                        $my_dep->setAttribute('xml:base', '');
11497
                                        if (strstr($file_path, $main_path) !== false) {
11498
                                            // The calculated real path is really inside the chamilo root path.
11499
                                            // Reduce file path to what's under the DocumentRoot.
11500
                                            $file_path = substr($file_path, strlen($root_path));
11501
                                            $zip_files_abs[] = $file_path;
11502
                                            $link_updates[$my_file_path][] = [
11503
                                                'orig' => $doc_info[0],
11504
                                                'dest' => 'document/'.$file_path,
11505
                                            ];
11506
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11507
                                            $my_dep->setAttribute('xml:base', '');
11508
                                        } elseif (empty($file_path)) {
11509
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11510
                                            $file_path = str_replace('//', '/', $file_path);
11511
                                            if (file_exists($file_path)) {
11512
                                                $file_path = substr($file_path, strlen($current_dir));
11513
                                                // We get the relative path.
11514
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11515
                                                $link_updates[$my_file_path][] = [
11516
                                                    'orig' => $doc_info[0],
11517
                                                    'dest' => 'document/'.$file_path,
11518
                                                ];
11519
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11520
                                                $my_dep->setAttribute('xml:base', '');
11521
                                            }
11522
                                        }
11523
                                        break;
11524
                                    case 'abs':
11525
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11526
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11527
                                        $current_dir = str_replace('\\', '/', $current_dir);
11528
                                        $file_path = realpath($doc_info[0]);
11529
                                        $file_path = str_replace('\\', '/', $file_path);
11530
                                        $my_dep_file->setAttribute('href', $file_path);
11531
                                        $my_dep->setAttribute('xml:base', '');
11532
11533
                                        if (strstr($file_path, $main_path) !== false) {
11534
                                            // The calculated real path is really inside the chamilo root path.
11535
                                            // Reduce file path to what's under the DocumentRoot.
11536
                                            $file_path = substr($file_path, strlen($root_path));
11537
                                            $zip_files_abs[] = $file_path;
11538
                                            $link_updates[$my_file_path][] = [
11539
                                                'orig' => $doc_info[0],
11540
                                                'dest' => $file_path,
11541
                                            ];
11542
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11543
                                            $my_dep->setAttribute('xml:base', '');
11544
                                        } elseif (empty($file_path)) {
11545
                                            $docSysPartPath = str_replace(
11546
                                                api_get_path(REL_COURSE_PATH),
11547
                                                '',
11548
                                                $doc_info[0]
11549
                                            );
11550
11551
                                            $docSysPartPathNoCourseCode = str_replace(
11552
                                                $_course['directory'].'/',
11553
                                                '',
11554
                                                $docSysPartPath
11555
                                            );
11556
11557
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11558
                                            if (file_exists($docSysPath)) {
11559
                                                $file_path = $docSysPartPathNoCourseCode;
11560
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11561
                                                $link_updates[$my_file_path][] = [
11562
                                                    'orig' => $doc_info[0],
11563
                                                    'dest' => $file_path,
11564
                                                ];
11565
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11566
                                                $my_dep->setAttribute('xml:base', '');
11567
                                            }
11568
                                        }
11569
                                        break;
11570
                                    case 'rel':
11571
                                        // Path relative to the current document. Save xml:base as current document's
11572
                                        // directory and save file in zip as subdir.file_path
11573
                                        if (substr($doc_info[0], 0, 2) === '..') {
11574
                                            // Relative path going up.
11575
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11576
                                            $current_dir = str_replace('\\', '/', $current_dir);
11577
                                            $file_path = realpath($current_dir.$doc_info[0]);
11578
                                            $file_path = str_replace('\\', '/', $file_path);
11579
                                            if (strstr($file_path, $main_path) !== false) {
11580
                                                // The calculated real path is really inside Chamilo's root path.
11581
                                                // Reduce file path to what's under the DocumentRoot.
11582
11583
                                                $file_path = substr($file_path, strlen($root_path));
11584
                                                $file_path_dest = $file_path;
11585
11586
                                                // File path is courses/CHAMILO/document/....
11587
                                                $info_file_path = explode('/', $file_path);
11588
                                                if ($info_file_path[0] == 'courses') {
11589
                                                    // Add character "/" in file path.
11590
                                                    $file_path_dest = 'document/'.$file_path;
11591
                                                }
11592
                                                $zip_files_abs[] = $file_path;
11593
11594
                                                $link_updates[$my_file_path][] = [
11595
                                                    'orig' => $doc_info[0],
11596
                                                    'dest' => $file_path_dest,
11597
                                                ];
11598
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11599
                                                $my_dep->setAttribute('xml:base', '');
11600
                                            }
11601
                                        } else {
11602
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11603
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11604
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11605
                                        }
11606
                                        break;
11607
                                    default:
11608
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11609
                                        $my_dep->setAttribute('xml:base', '');
11610
                                        break;
11611
                                }
11612
                            }
11613
                            $my_dep->appendChild($my_dep_file);
11614
                            $resources->appendChild($my_dep);
11615
                            $dependency = $xmldoc->createElement('dependency');
11616
                            $dependency->setAttribute('identifierref', $res_id);
11617
                            $my_resource->appendChild($dependency);
11618
                            $i++;
11619
                        }
11620
                        $resources->appendChild($my_resource);
11621
                        $zip_files[] = $my_file_path;
11622
                        break;
11623
                    default:
11624
                        // Get the path of the file(s) from the course directory root
11625
                        $my_file_path = 'non_exportable.html';
11626
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11627
                        $my_xml_file_path = $my_file_path;
11628
                        $my_sub_dir = dirname($my_file_path);
11629
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11630
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11631
                        $my_xml_sub_dir = $my_sub_dir;
11632
                        // Give a <resource> child to the <resources> element.
11633
                        $my_resource = $xmldoc->createElement('resource');
11634
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11635
                        $my_resource->setAttribute('type', 'webcontent');
11636
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11637
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11638
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11639
                        // xml:base is the base directory to find the files declared in this resource.
11640
                        $my_resource->setAttribute('xml:base', '');
11641
                        // Give a <file> child to the <resource> element.
11642
                        $my_file = $xmldoc->createElement('file');
11643
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11644
                        $my_resource->appendChild($my_file);
11645
                        $resources->appendChild($my_resource);
11646
                        break;
11647
                }
11648
            }
11649
        }
11650
        $organizations->appendChild($organization);
11651
        $root->appendChild($organizations);
11652
        $root->appendChild($resources);
11653
        $xmldoc->appendChild($root);
11654
11655
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11656
11657
        // then add the file to the zip, then destroy the file (this is done automatically).
11658
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11659
        foreach ($zip_files as $file_path) {
11660
            if (empty($file_path)) {
11661
                continue;
11662
            }
11663
11664
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11665
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11666
11667
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11668
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11669
            }
11670
11671
            $this->create_path($dest_file);
11672
            @copy($filePath, $dest_file);
11673
11674
            // Check if the file needs a link update.
11675
            if (in_array($file_path, array_keys($link_updates))) {
11676
                $string = file_get_contents($dest_file);
11677
                unlink($dest_file);
11678
                foreach ($link_updates[$file_path] as $old_new) {
11679
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11680
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11681
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11682
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11683
                    if (substr($old_new['dest'], -3) === 'flv' &&
11684
                        substr($old_new['dest'], 0, 5) === 'main/'
11685
                    ) {
11686
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11687
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11688
                        substr($old_new['dest'], 0, 6) === 'video/'
11689
                    ) {
11690
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11691
                    }
11692
11693
                    // Fix to avoid problems with default_course_document
11694
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11695
                        $newDestination = $old_new['dest'];
11696
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11697
                            $newDestination = $old_new['replace'];
11698
                        }
11699
                    } else {
11700
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11701
                    }
11702
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11703
11704
                    // Add files inside the HTMLs
11705
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11706
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11707
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11708
                        copy(
11709
                            $sys_course_path.$new_path,
11710
                            $destinationFile
11711
                        );
11712
                    }
11713
                }
11714
                file_put_contents($dest_file, $string);
11715
            }
11716
11717
            if (file_exists($filePath) && $copyAll) {
11718
                $extension = $this->get_extension($filePath);
11719
                if (in_array($extension, ['html', 'html'])) {
11720
                    $containerOrigin = dirname($filePath);
11721
                    $containerDestination = dirname($dest_file);
11722
11723
                    $finder = new Finder();
11724
                    $finder->files()->in($containerOrigin)
11725
                        ->notName('*_DELETED_*')
11726
                        ->exclude('share_folder')
11727
                        ->exclude('chat_files')
11728
                        ->exclude('certificates')
11729
                    ;
11730
11731
                    if (is_dir($containerOrigin) &&
11732
                        is_dir($containerDestination)
11733
                    ) {
11734
                        $fs = new Filesystem();
11735
                        $fs->mirror(
11736
                            $containerOrigin,
11737
                            $containerDestination,
11738
                            $finder
11739
                        );
11740
                    }
11741
                }
11742
            }
11743
        }
11744
11745
        foreach ($zip_files_abs as $file_path) {
11746
            if (empty($file_path)) {
11747
                continue;
11748
            }
11749
11750
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11751
                continue;
11752
            }
11753
11754
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11755
            if (strstr($file_path, 'upload/users') !== false) {
11756
                $pos = strpos($file_path, 'my_files/');
11757
                if ($pos !== false) {
11758
                    $onlyDirectory = str_replace(
11759
                        'upload/users/',
11760
                        '',
11761
                        substr($file_path, $pos, strlen($file_path))
11762
                    );
11763
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11764
                }
11765
            }
11766
11767
            if (strstr($file_path, 'default_course_document/') !== false) {
11768
                $replace = str_replace('/main', '', $file_path);
11769
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11770
            }
11771
11772
            if (empty($dest_file)) {
11773
                continue;
11774
            }
11775
11776
            $this->create_path($dest_file);
11777
            copy($main_path.$file_path, $dest_file);
11778
            // Check if the file needs a link update.
11779
            if (in_array($file_path, array_keys($link_updates))) {
11780
                $string = file_get_contents($dest_file);
11781
                unlink($dest_file);
11782
                foreach ($link_updates[$file_path] as $old_new) {
11783
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11784
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11785
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11786
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11787
                    if (substr($old_new['dest'], -3) == 'flv' &&
11788
                        substr($old_new['dest'], 0, 5) == 'main/'
11789
                    ) {
11790
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11791
                    }
11792
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11793
                }
11794
                file_put_contents($dest_file, $string);
11795
            }
11796
        }
11797
11798
        if (is_array($links_to_create)) {
11799
            foreach ($links_to_create as $file => $link) {
11800
                $content = '<!DOCTYPE html><head>
11801
                            <meta charset="'.api_get_language_isocode().'" />
11802
                            <title>'.$link['title'].'</title>
11803
                            </head>
11804
                            <body dir="'.api_get_text_direction().'">
11805
                            <div style="text-align:center">
11806
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11807
                            </body>
11808
                            </html>';
11809
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11810
            }
11811
        }
11812
11813
        // Add non exportable message explanation.
11814
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11815
        $file_content = '<!DOCTYPE html><head>
11816
                        <meta charset="'.api_get_language_isocode().'" />
11817
                        <title>'.$lang_not_exportable.'</title>
11818
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11819
                        </head>
11820
                        <body dir="'.api_get_text_direction().'">';
11821
        $file_content .=
11822
            <<<EOD
11823
                    <style>
11824
            .error-message {
11825
                font-family: arial, verdana, helvetica, sans-serif;
11826
                border-width: 1px;
11827
                border-style: solid;
11828
                left: 50%;
11829
                margin: 10px auto;
11830
                min-height: 30px;
11831
                padding: 5px;
11832
                right: 50%;
11833
                width: 500px;
11834
                background-color: #FFD1D1;
11835
                border-color: #FF0000;
11836
                color: #000;
11837
            }
11838
        </style>
11839
    <body>
11840
        <div class="error-message">
11841
            $lang_not_exportable
11842
        </div>
11843
    </body>
11844
</html>
11845
EOD;
11846
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11847
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11848
        }
11849
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11850
11851
        // Add the extra files that go along with a SCORM package.
11852
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11853
11854
        $fs = new Filesystem();
11855
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11856
11857
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11858
        $manifest = @$xmldoc->saveXML();
11859
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11860
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11861
        $zip_folder->add(
11862
            $archivePath.'/'.$temp_dir_short,
11863
            PCLZIP_OPT_REMOVE_PATH,
11864
            $archivePath.'/'.$temp_dir_short.'/'
11865
        );
11866
11867
        // Clean possible temporary files.
11868
        foreach ($files_cleanup as $file) {
11869
            $res = unlink($file);
11870
            if ($res === false) {
11871
                error_log(
11872
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11873
                    0
11874
                );
11875
            }
11876
        }
11877
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11878
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11879
    }
11880
11881
    /**
11882
     * @param int $lp_id
11883
     *
11884
     * @return bool
11885
     */
11886
    public function scorm_export_to_pdf($lp_id)
11887
    {
11888
        $lp_id = (int) $lp_id;
11889
        $files_to_export = [];
11890
11891
        $sessionId = api_get_session_id();
11892
        $course_data = api_get_course_info($this->cc);
11893
11894
        if (!empty($course_data)) {
11895
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11896
            $list = self::get_flat_ordered_items_list($lp_id);
11897
            if (!empty($list)) {
11898
                foreach ($list as $item_id) {
11899
                    $item = $this->items[$item_id];
11900
                    switch ($item->type) {
11901
                        case 'document':
11902
                            // Getting documents from a LP with chamilo documents
11903
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11904
                            // Try loading document from the base course.
11905
                            if (empty($file_data) && !empty($sessionId)) {
11906
                                $file_data = DocumentManager::get_document_data_by_id(
11907
                                    $item->path,
11908
                                    $this->cc,
11909
                                    false,
11910
                                    0
11911
                                );
11912
                            }
11913
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11914
                            if (file_exists($file_path)) {
11915
                                $files_to_export[] = [
11916
                                    'title' => $item->get_title(),
11917
                                    'path' => $file_path,
11918
                                ];
11919
                            }
11920
                            break;
11921
                        case 'asset': //commes from a scorm package generated by chamilo
11922
                        case 'sco':
11923
                            $file_path = $scorm_path.'/'.$item->path;
11924
                            if (file_exists($file_path)) {
11925
                                $files_to_export[] = [
11926
                                    'title' => $item->get_title(),
11927
                                    'path' => $file_path,
11928
                                ];
11929
                            }
11930
                            break;
11931
                        case 'dir':
11932
                            $files_to_export[] = [
11933
                                'title' => $item->get_title(),
11934
                                'path' => null,
11935
                            ];
11936
                            break;
11937
                    }
11938
                }
11939
            }
11940
11941
            $pdf = new PDF();
11942
            $result = $pdf->html_to_pdf(
11943
                $files_to_export,
11944
                $this->name,
11945
                $this->cc,
11946
                true,
11947
                true,
11948
                true,
11949
                $this->get_name()
11950
            );
11951
11952
            return $result;
11953
        }
11954
11955
        return false;
11956
    }
11957
11958
    /**
11959
     * Temp function to be moved in main_api or the best place around for this.
11960
     * Creates a file path if it doesn't exist.
11961
     *
11962
     * @param string $path
11963
     */
11964
    public function create_path($path)
11965
    {
11966
        $path_bits = explode('/', dirname($path));
11967
11968
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11969
        $path_built = IS_WINDOWS_OS ? '' : '/';
11970
        foreach ($path_bits as $bit) {
11971
            if (!empty($bit)) {
11972
                $new_path = $path_built.$bit;
11973
                if (is_dir($new_path)) {
11974
                    $path_built = $new_path.'/';
11975
                } else {
11976
                    mkdir($new_path, api_get_permissions_for_new_directories());
11977
                    $path_built = $new_path.'/';
11978
                }
11979
            }
11980
        }
11981
    }
11982
11983
    /**
11984
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11985
     *
11986
     * @return bool The results of the unlink function, or false if there was no image to start with
11987
     */
11988
    public function delete_lp_image()
11989
    {
11990
        $img = $this->get_preview_image();
11991
        if ($img != '') {
11992
            $del_file = $this->get_preview_image_path(null, 'sys');
11993
            if (isset($del_file) && file_exists($del_file)) {
11994
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11995
                if (file_exists($del_file_2)) {
11996
                    unlink($del_file_2);
11997
                }
11998
                $this->set_preview_image('');
11999
12000
                return @unlink($del_file);
12001
            }
12002
        }
12003
12004
        return false;
12005
    }
12006
12007
    /**
12008
     * Uploads an author image to the upload/learning_path/images directory.
12009
     *
12010
     * @param array    The image array, coming from the $_FILES superglobal
12011
     *
12012
     * @return bool True on success, false on error
12013
     */
12014
    public function upload_image($image_array)
12015
    {
12016
        if (!empty($image_array['name'])) {
12017
            $upload_ok = process_uploaded_file($image_array);
12018
            $has_attachment = true;
12019
        }
12020
12021
        if ($upload_ok && $has_attachment) {
12022
            $courseDir = api_get_course_path().'/upload/learning_path/images';
12023
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
12024
            $updir = $sys_course_path.$courseDir;
12025
            // Try to add an extension to the file if it hasn't one.
12026
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
12027
12028
            if (filter_extension($new_file_name)) {
12029
                $file_extension = explode('.', $image_array['name']);
12030
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
12031
                $filename = uniqid('');
12032
                $new_file_name = $filename.'.'.$file_extension;
12033
                $new_path = $updir.'/'.$new_file_name;
12034
12035
                // Resize the image.
12036
                $temp = new Image($image_array['tmp_name']);
12037
                $temp->resize(104);
12038
                $result = $temp->send_image($new_path);
12039
12040
                // Storing the image filename.
12041
                if ($result) {
12042
                    $this->set_preview_image($new_file_name);
12043
12044
                    //Resize to 64px to use on course homepage
12045
                    $temp->resize(64);
12046
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
12047
12048
                    return true;
12049
                }
12050
            }
12051
        }
12052
12053
        return false;
12054
    }
12055
12056
    /**
12057
     * @param int    $lp_id
12058
     * @param string $status
12059
     */
12060
    public function set_autolaunch($lp_id, $status)
12061
    {
12062
        $course_id = api_get_course_int_id();
12063
        $lp_id = (int) $lp_id;
12064
        $status = (int) $status;
12065
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12066
12067
        // Setting everything to autolaunch = 0
12068
        $attributes['autolaunch'] = 0;
12069
        $where = [
12070
            'session_id = ? AND c_id = ? ' => [
12071
                api_get_session_id(),
12072
                $course_id,
12073
            ],
12074
        ];
12075
        Database::update($lp_table, $attributes, $where);
12076
        if ($status == 1) {
12077
            //Setting my lp_id to autolaunch = 1
12078
            $attributes['autolaunch'] = 1;
12079
            $where = [
12080
                'iid = ? AND session_id = ? AND c_id = ?' => [
12081
                    $lp_id,
12082
                    api_get_session_id(),
12083
                    $course_id,
12084
                ],
12085
            ];
12086
            Database::update($lp_table, $attributes, $where);
12087
        }
12088
    }
12089
12090
    /**
12091
     * Gets previous_item_id for the next element of the lp_item table.
12092
     *
12093
     * @author Isaac flores paz
12094
     *
12095
     * @return int Previous item ID
12096
     */
12097
    public function select_previous_item_id()
12098
    {
12099
        $course_id = api_get_course_int_id();
12100
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12101
12102
        // Get the max order of the items
12103
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
12104
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
12105
        $rs_max_order = Database::query($sql);
12106
        $row_max_order = Database::fetch_object($rs_max_order);
12107
        $max_order = $row_max_order->display_order;
12108
        // Get the previous item ID
12109
        $sql = "SELECT iid as previous FROM $table_lp_item
12110
                WHERE
12111
                    c_id = $course_id AND
12112
                    lp_id = ".$this->lp_id." AND
12113
                    display_order = '$max_order' ";
12114
        $rs_max = Database::query($sql);
12115
        $row_max = Database::fetch_object($rs_max);
12116
12117
        // Return the previous item ID
12118
        return $row_max->previous;
12119
    }
12120
12121
    /**
12122
     * Copies an LP.
12123
     */
12124
    public function copy()
12125
    {
12126
        // Course builder
12127
        $cb = new CourseBuilder();
12128
12129
        //Setting tools that will be copied
12130
        $cb->set_tools_to_build(['learnpaths']);
12131
12132
        //Setting elements that will be copied
12133
        $cb->set_tools_specific_id_list(['learnpaths' => [$this->lp_id]]);
12134
12135
        $course = $cb->build();
12136
12137
        //Course restorer
12138
        $course_restorer = new CourseRestorer($course);
12139
        $course_restorer->set_add_text_in_items(true);
12140
        $course_restorer->set_tool_copy_settings(
12141
            ['learnpaths' => ['reset_dates' => true]]
12142
        );
12143
        $course_restorer->restore(
12144
            api_get_course_id(),
12145
            api_get_session_id(),
12146
            false,
12147
            false
12148
        );
12149
    }
12150
12151
    /**
12152
     * Verify document size.
12153
     *
12154
     * @param string $s
12155
     *
12156
     * @return bool
12157
     */
12158
    public static function verify_document_size($s)
12159
    {
12160
        $post_max = ini_get('post_max_size');
12161
        if (substr($post_max, -1, 1) == 'M') {
12162
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
12163
        } elseif (substr($post_max, -1, 1) == 'G') {
12164
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
12165
        }
12166
        $upl_max = ini_get('upload_max_filesize');
12167
        if (substr($upl_max, -1, 1) == 'M') {
12168
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
12169
        } elseif (substr($upl_max, -1, 1) == 'G') {
12170
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
12171
        }
12172
        $documents_total_space = DocumentManager::documents_total_space();
12173
        $course_max_space = DocumentManager::get_course_quota();
12174
        $total_size = filesize($s) + $documents_total_space;
12175
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12176
            return true;
12177
        }
12178
12179
        return false;
12180
    }
12181
12182
    /**
12183
     * Clear LP prerequisites.
12184
     */
12185
    public function clear_prerequisites()
12186
    {
12187
        $course_id = $this->get_course_int_id();
12188
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12189
        $lp_id = $this->get_id();
12190
        // Cleaning prerequisites
12191
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12192
                WHERE c_id = $course_id AND lp_id = $lp_id";
12193
        Database::query($sql);
12194
12195
        // Cleaning mastery score for exercises
12196
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12197
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12198
        Database::query($sql);
12199
    }
12200
12201
    public function set_previous_step_as_prerequisite_for_all_items()
12202
    {
12203
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12204
        $course_id = $this->get_course_int_id();
12205
        $lp_id = $this->get_id();
12206
12207
        if (!empty($this->items)) {
12208
            $previous_item_id = null;
12209
            $previous_item_max = 0;
12210
            $previous_item_type = null;
12211
            $last_item_not_dir = null;
12212
            $last_item_not_dir_type = null;
12213
            $last_item_not_dir_max = null;
12214
12215
            foreach ($this->ordered_items as $itemId) {
12216
                $item = $this->getItem($itemId);
12217
                // if there was a previous item... (otherwise jump to set it)
12218
                if (!empty($previous_item_id)) {
12219
                    $current_item_id = $item->get_id(); //save current id
12220
                    if ($item->get_type() != 'dir') {
12221
                        // Current item is not a folder, so it qualifies to get a prerequisites
12222
                        if ($last_item_not_dir_type == 'quiz') {
12223
                            // if previous is quiz, mark its max score as default score to be achieved
12224
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12225
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12226
                            Database::query($sql);
12227
                        }
12228
                        // now simply update the prerequisite to set it to the last non-chapter item
12229
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12230
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12231
                        Database::query($sql);
12232
                        // record item as 'non-chapter' reference
12233
                        $last_item_not_dir = $item->get_id();
12234
                        $last_item_not_dir_type = $item->get_type();
12235
                        $last_item_not_dir_max = $item->get_max();
12236
                    }
12237
                } else {
12238
                    if ($item->get_type() != 'dir') {
12239
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12240
                        $last_item_not_dir = $item->get_id();
12241
                        $last_item_not_dir_type = $item->get_type();
12242
                        $last_item_not_dir_max = $item->get_max();
12243
                    }
12244
                }
12245
                // Saving the item as "previous item" for the next loop
12246
                $previous_item_id = $item->get_id();
12247
                $previous_item_max = $item->get_max();
12248
                $previous_item_type = $item->get_type();
12249
            }
12250
        }
12251
    }
12252
12253
    /**
12254
     * @param array $params
12255
     *
12256
     * @throws \Doctrine\ORM\OptimisticLockException
12257
     *
12258
     * @return int
12259
     */
12260
    public static function createCategory($params)
12261
    {
12262
        $em = Database::getManager();
12263
        $item = new CLpCategory();
12264
        $item->setName($params['name']);
12265
        $item->setCId($params['c_id']);
12266
        $em->persist($item);
12267
        $em->flush();
12268
12269
        $id = $item->getId();
12270
12271
        $sessionId = api_get_session_id();
12272
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12273
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12274
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12275
            Database::query($sql);
12276
        }
12277
12278
        api_item_property_update(
12279
            api_get_course_info(),
12280
            TOOL_LEARNPATH_CATEGORY,
12281
            $id,
12282
            'visible',
12283
            api_get_user_id()
12284
        );
12285
12286
        return $item->getId();
12287
    }
12288
12289
    /**
12290
     * @param array $params
12291
     */
12292
    public static function updateCategory($params)
12293
    {
12294
        $em = Database::getManager();
12295
        $item = self::getCategory($params['id']);
12296
12297
        if ($item) {
12298
            $item->setName($params['name']);
12299
            $em->persist($item);
12300
            $em->flush();
12301
        }
12302
    }
12303
12304
    /**
12305
     * @param int $id
12306
     */
12307
    public static function moveUpCategory($id)
12308
    {
12309
        $item = self::getCategory($id);
12310
        if ($item) {
12311
            $em = Database::getManager();
12312
            $position = $item->getPosition() - 1;
12313
            $item->setPosition($position);
12314
            $em->persist($item);
12315
            $em->flush();
12316
        }
12317
    }
12318
12319
    /**
12320
     * @param int $id
12321
     *
12322
     * @throws \Doctrine\ORM\ORMException
12323
     * @throws \Doctrine\ORM\OptimisticLockException
12324
     * @throws \Doctrine\ORM\TransactionRequiredException
12325
     */
12326
    public static function moveDownCategory($id)
12327
    {
12328
        $item = self::getCategory($id);
12329
        if ($item) {
12330
            $em = Database::getManager();
12331
            $position = $item->getPosition() + 1;
12332
            $item->setPosition($position);
12333
            $em->persist($item);
12334
            $em->flush();
12335
        }
12336
    }
12337
12338
    public static function getLpList($courseId, $sessionId, $onlyActiveLp = true)
12339
    {
12340
        $TABLE_LP = Database::get_course_table(TABLE_LP_MAIN);
12341
        $TABLE_ITEM_PROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
12342
        $courseId = (int) $courseId;
12343
        $sessionId = (int) $sessionId;
12344
12345
        $sql = "SELECT lp.id, lp.name
12346
                FROM $TABLE_LP lp
12347
                INNER JOIN $TABLE_ITEM_PROPERTY ip
12348
                ON lp.id = ip.ref
12349
                WHERE lp.c_id = $courseId ";
12350
12351
        if (!empty($sessionId)) {
12352
            $sql .= "AND ip.session_id = $sessionId ";
12353
        }
12354
12355
        if ($onlyActiveLp) {
12356
            $sql .= "AND ip.tool = 'learnpath' ";
12357
            $sql .= "AND ip.visibility = 1 ";
12358
        }
12359
12360
        $sql .= "GROUP BY lp.id";
12361
12362
        $result = Database::query($sql);
12363
12364
        return Database::store_result($result, 'ASSOC');
12365
    }
12366
12367
    /**
12368
     * @param int $courseId
12369
     *
12370
     * @throws \Doctrine\ORM\Query\QueryException
12371
     *
12372
     * @return int|mixed
12373
     */
12374
    public static function getCountCategories($courseId)
12375
    {
12376
        if (empty($courseId)) {
12377
            return 0;
12378
        }
12379
        $em = Database::getManager();
12380
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12381
        $query->setParameter('id', $courseId);
12382
12383
        return $query->getSingleScalarResult();
12384
    }
12385
12386
    /**
12387
     * @param int $courseId
12388
     *
12389
     * @return CLpCategory[]
12390
     */
12391
    public static function getCategories($courseId)
12392
    {
12393
        $em = Database::getManager();
12394
12395
        // Using doctrine extensions
12396
        /** @var SortableRepository $repo */
12397
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12398
12399
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12400
    }
12401
12402
    public static function getCategorySessionId($id)
12403
    {
12404
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12405
            return 0;
12406
        }
12407
12408
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12409
        $id = (int) $id;
12410
12411
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12412
        $result = Database::query($sql);
12413
        $result = Database::fetch_array($result, 'ASSOC');
12414
12415
        if ($result) {
12416
            return (int) $result['session_id'];
12417
        }
12418
12419
        return 0;
12420
    }
12421
12422
    /**
12423
     * @param int $id
12424
     *
12425
     * @return CLpCategory
12426
     */
12427
    public static function getCategory($id)
12428
    {
12429
        $id = (int) $id;
12430
        $em = Database::getManager();
12431
12432
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12433
    }
12434
12435
    /**
12436
     * @param int $courseId
12437
     *
12438
     * @return CLpCategory[]
12439
     */
12440
    public static function getCategoryByCourse($courseId)
12441
    {
12442
        $em = Database::getManager();
12443
12444
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12445
    }
12446
12447
    /**
12448
     * @param int $id
12449
     *
12450
     * @return bool
12451
     */
12452
    public static function deleteCategory($id)
12453
    {
12454
        $em = Database::getManager();
12455
        $id = (int) $id;
12456
        $item = self::getCategory($id);
12457
        if ($item) {
12458
            $courseId = $item->getCId();
12459
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12460
            $query->setParameter('id', $courseId);
12461
            $query->setParameter('catId', $item->getId());
12462
            $lps = $query->getResult();
12463
12464
            // Setting category = 0.
12465
            if ($lps) {
12466
                foreach ($lps as $lpItem) {
12467
                    $lpItem->setCategoryId(0);
12468
                }
12469
            }
12470
12471
            if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
12472
                $table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
12473
                $sql = "DELETE FROM $table
12474
                        WHERE
12475
                            lp_category_id = $id AND
12476
                            c_id = $courseId ";
12477
                Database::query($sql);
12478
            }
12479
12480
            // Removing category.
12481
            $em->remove($item);
12482
            $em->flush();
12483
12484
            $courseInfo = api_get_course_info_by_id($courseId);
12485
            $sessionId = api_get_session_id();
12486
12487
            // Delete link tool
12488
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12489
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12490
            // Delete tools
12491
            $sql = "DELETE FROM $tbl_tool
12492
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12493
            Database::query($sql);
12494
12495
            return true;
12496
        }
12497
12498
        return false;
12499
    }
12500
12501
    /**
12502
     * @param int  $courseId
12503
     * @param bool $addSelectOption
12504
     *
12505
     * @return mixed
12506
     */
12507
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12508
    {
12509
        $items = self::getCategoryByCourse($courseId);
12510
        $cats = [];
12511
        if ($addSelectOption) {
12512
            $cats = [get_lang('SelectACategory')];
12513
        }
12514
12515
        $sessionId = api_get_session_id();
12516
        $avoidCategoryInSession = false;
12517
        if (empty($sessionId)) {
12518
            $avoidCategoryInSession = true;
12519
        }
12520
        /*$checkSession = false;
12521
        if (api_get_configuration_value('allow_session_lp_category')) {
12522
            $checkSession = true;
12523
        }*/
12524
12525
        if (!empty($items)) {
12526
            foreach ($items as $cat) {
12527
                $categoryId = $cat->getId();
12528
                if ($avoidCategoryInSession) {
12529
                    $inSession = self::getCategorySessionId($categoryId);
12530
                    if (!empty($inSession)) {
12531
                        continue;
12532
                    }
12533
                }
12534
                $cats[$categoryId] = $cat->getName();
12535
            }
12536
        }
12537
12538
        return $cats;
12539
    }
12540
12541
    /**
12542
     * @param string $courseCode
12543
     * @param int    $lpId
12544
     * @param int    $user_id
12545
     *
12546
     * @return learnpath
12547
     */
12548
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12549
    {
12550
        $debug = 0;
12551
        $learnPath = null;
12552
        $lpObject = Session::read('lpobject');
12553
        if ($lpObject !== null) {
12554
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12555
            if ($debug) {
12556
                error_log('getLpFromSession: unserialize');
12557
                error_log('------getLpFromSession------');
12558
                error_log('------unserialize------');
12559
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12560
                error_log("api_get_sessionid: ".api_get_session_id());
12561
            }
12562
        }
12563
12564
        if (!is_object($learnPath)) {
12565
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12566
            if ($debug) {
12567
                error_log('------getLpFromSession------');
12568
                error_log('getLpFromSession: create new learnpath');
12569
                error_log("create new LP with $courseCode - $lpId - $user_id");
12570
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12571
                error_log("api_get_sessionid: ".api_get_session_id());
12572
            }
12573
        }
12574
12575
        return $learnPath;
12576
    }
12577
12578
    /**
12579
     * @param int $itemId
12580
     *
12581
     * @return learnpathItem|false
12582
     */
12583
    public function getItem($itemId)
12584
    {
12585
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12586
            return $this->items[$itemId];
12587
        }
12588
12589
        return false;
12590
    }
12591
12592
    /**
12593
     * @return int
12594
     */
12595
    public function getCurrentAttempt()
12596
    {
12597
        $attempt = $this->getItem($this->get_current_item_id());
12598
        if ($attempt) {
12599
            $attemptId = $attempt->get_attempt_id();
12600
12601
            return $attemptId;
12602
        }
12603
12604
        return 0;
12605
    }
12606
12607
    /**
12608
     * @return int
12609
     */
12610
    public function getCategoryId()
12611
    {
12612
        return (int) $this->categoryId;
12613
    }
12614
12615
    /**
12616
     * @param int $categoryId
12617
     *
12618
     * @return bool
12619
     */
12620
    public function setCategoryId($categoryId)
12621
    {
12622
        $this->categoryId = (int) $categoryId;
12623
        $table = Database::get_course_table(TABLE_LP_MAIN);
12624
        $lp_id = $this->get_id();
12625
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12626
                WHERE iid = $lp_id";
12627
        Database::query($sql);
12628
12629
        return true;
12630
    }
12631
12632
    /**
12633
     * Get whether this is a learning path with the possibility to subscribe
12634
     * users or not.
12635
     *
12636
     * @return int
12637
     */
12638
    public function getSubscribeUsers()
12639
    {
12640
        return $this->subscribeUsers;
12641
    }
12642
12643
    /**
12644
     * Set whether this is a learning path with the possibility to subscribe
12645
     * users or not.
12646
     *
12647
     * @param int $value (0 = false, 1 = true)
12648
     *
12649
     * @return bool
12650
     */
12651
    public function setSubscribeUsers($value)
12652
    {
12653
        $this->subscribeUsers = (int) $value;
12654
        $table = Database::get_course_table(TABLE_LP_MAIN);
12655
        $lp_id = $this->get_id();
12656
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12657
                WHERE iid = $lp_id";
12658
        Database::query($sql);
12659
12660
        return true;
12661
    }
12662
12663
    /**
12664
     * Calculate the count of stars for a user in this LP
12665
     * This calculation is based on the following rules:
12666
     * - the student gets one star when he gets to 50% of the learning path
12667
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12668
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12669
     * - the student gets the final star when the score for the *last* test is >= 80%.
12670
     *
12671
     * @param int $sessionId Optional. The session ID
12672
     *
12673
     * @return int The count of stars
12674
     */
12675
    public function getCalculateStars($sessionId = 0)
12676
    {
12677
        $stars = 0;
12678
        $progress = self::getProgress(
12679
            $this->lp_id,
12680
            $this->user_id,
12681
            $this->course_int_id,
12682
            $sessionId
12683
        );
12684
12685
        if ($progress >= 50) {
12686
            $stars++;
12687
        }
12688
12689
        // Calculate stars chapters evaluation
12690
        $exercisesItems = $this->getExercisesItems();
12691
12692
        if (!empty($exercisesItems)) {
12693
            $totalResult = 0;
12694
12695
            foreach ($exercisesItems as $exerciseItem) {
12696
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12697
                    $this->user_id,
12698
                    $exerciseItem->path,
12699
                    $this->course_int_id,
12700
                    $sessionId,
12701
                    $this->lp_id,
12702
                    $exerciseItem->db_id
12703
                );
12704
12705
                $exerciseResultInfo = end($exerciseResultInfo);
12706
12707
                if (!$exerciseResultInfo) {
12708
                    continue;
12709
                }
12710
12711
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12712
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12713
                } else {
12714
                    $exerciseResult = 0;
12715
                }
12716
                $totalResult += $exerciseResult;
12717
            }
12718
12719
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12720
12721
            if ($totalExerciseAverage >= 50) {
12722
                $stars++;
12723
            }
12724
12725
            if ($totalExerciseAverage >= 80) {
12726
                $stars++;
12727
            }
12728
        }
12729
12730
        // Calculate star for final evaluation
12731
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12732
12733
        if (!empty($finalEvaluationItem)) {
12734
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12735
                $this->user_id,
12736
                $finalEvaluationItem->path,
12737
                $this->course_int_id,
12738
                $sessionId,
12739
                $this->lp_id,
12740
                $finalEvaluationItem->db_id
12741
            );
12742
12743
            $evaluationResultInfo = end($evaluationResultInfo);
12744
12745
            if ($evaluationResultInfo) {
12746
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12747
12748
                if ($evaluationResult >= 80) {
12749
                    $stars++;
12750
                }
12751
            }
12752
        }
12753
12754
        return $stars;
12755
    }
12756
12757
    /**
12758
     * Get the items of exercise type.
12759
     *
12760
     * @return array The items. Otherwise return false
12761
     */
12762
    public function getExercisesItems()
12763
    {
12764
        $exercises = [];
12765
        foreach ($this->items as $item) {
12766
            if ($item->type != 'quiz') {
12767
                continue;
12768
            }
12769
            $exercises[] = $item;
12770
        }
12771
12772
        array_pop($exercises);
12773
12774
        return $exercises;
12775
    }
12776
12777
    /**
12778
     * Get the item of exercise type (evaluation type).
12779
     *
12780
     * @return array The final evaluation. Otherwise return false
12781
     */
12782
    public function getFinalEvaluationItem()
12783
    {
12784
        $exercises = [];
12785
        foreach ($this->items as $item) {
12786
            if ($item->type != 'quiz') {
12787
                continue;
12788
            }
12789
12790
            $exercises[] = $item;
12791
        }
12792
12793
        return array_pop($exercises);
12794
    }
12795
12796
    /**
12797
     * Calculate the total points achieved for the current user in this learning path.
12798
     *
12799
     * @param int $sessionId Optional. The session Id
12800
     *
12801
     * @return int
12802
     */
12803
    public function getCalculateScore($sessionId = 0)
12804
    {
12805
        // Calculate stars chapters evaluation
12806
        $exercisesItems = $this->getExercisesItems();
12807
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12808
        $totalExercisesResult = 0;
12809
        $totalEvaluationResult = 0;
12810
12811
        if ($exercisesItems !== false) {
12812
            foreach ($exercisesItems as $exerciseItem) {
12813
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12814
                    $this->user_id,
12815
                    $exerciseItem->path,
12816
                    $this->course_int_id,
12817
                    $sessionId,
12818
                    $this->lp_id,
12819
                    $exerciseItem->db_id
12820
                );
12821
12822
                $exerciseResultInfo = end($exerciseResultInfo);
12823
12824
                if (!$exerciseResultInfo) {
12825
                    continue;
12826
                }
12827
12828
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12829
            }
12830
        }
12831
12832
        if (!empty($finalEvaluationItem)) {
12833
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12834
                $this->user_id,
12835
                $finalEvaluationItem->path,
12836
                $this->course_int_id,
12837
                $sessionId,
12838
                $this->lp_id,
12839
                $finalEvaluationItem->db_id
12840
            );
12841
12842
            $evaluationResultInfo = end($evaluationResultInfo);
12843
12844
            if ($evaluationResultInfo) {
12845
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12846
            }
12847
        }
12848
12849
        return $totalExercisesResult + $totalEvaluationResult;
12850
    }
12851
12852
    /**
12853
     * Check if URL is not allowed to be show in a iframe.
12854
     *
12855
     * @param string $src
12856
     *
12857
     * @return string
12858
     */
12859
    public function fixBlockedLinks($src)
12860
    {
12861
        $urlInfo = parse_url($src);
12862
12863
        $platformProtocol = 'https';
12864
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12865
            $platformProtocol = 'http';
12866
        }
12867
12868
        $protocolFixApplied = false;
12869
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12870
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12871
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12872
12873
        if ($platformProtocol != $scheme) {
12874
            Session::write('x_frame_source', $src);
12875
            $src = 'blank.php?error=x_frames_options';
12876
            $protocolFixApplied = true;
12877
        }
12878
12879
        if ($protocolFixApplied == false) {
12880
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12881
                // Check X-Frame-Options
12882
                $ch = curl_init();
12883
                $options = [
12884
                    CURLOPT_URL => $src,
12885
                    CURLOPT_RETURNTRANSFER => true,
12886
                    CURLOPT_HEADER => true,
12887
                    CURLOPT_FOLLOWLOCATION => true,
12888
                    CURLOPT_ENCODING => "",
12889
                    CURLOPT_AUTOREFERER => true,
12890
                    CURLOPT_CONNECTTIMEOUT => 120,
12891
                    CURLOPT_TIMEOUT => 120,
12892
                    CURLOPT_MAXREDIRS => 10,
12893
                ];
12894
12895
                $proxySettings = api_get_configuration_value('proxy_settings');
12896
                if (!empty($proxySettings) &&
12897
                    isset($proxySettings['curl_setopt_array'])
12898
                ) {
12899
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12900
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12901
                }
12902
12903
                curl_setopt_array($ch, $options);
12904
                $response = curl_exec($ch);
12905
                $httpCode = curl_getinfo($ch);
12906
                $headers = substr($response, 0, $httpCode['header_size']);
12907
12908
                $error = false;
12909
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12910
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12911
                ) {
12912
                    $error = true;
12913
                }
12914
12915
                if ($error) {
12916
                    Session::write('x_frame_source', $src);
12917
                    $src = 'blank.php?error=x_frames_options';
12918
                }
12919
            }
12920
        }
12921
12922
        return $src;
12923
    }
12924
12925
    /**
12926
     * Check if this LP has a created forum in the basis course.
12927
     *
12928
     * @return bool
12929
     */
12930
    public function lpHasForum()
12931
    {
12932
        $forumTable = Database::get_course_table(TABLE_FORUM);
12933
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12934
12935
        $fakeFrom = "
12936
            $forumTable f
12937
            INNER JOIN $itemProperty ip
12938
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12939
        ";
12940
12941
        $resultData = Database::select(
12942
            'COUNT(f.iid) AS qty',
12943
            $fakeFrom,
12944
            [
12945
                'where' => [
12946
                    'ip.visibility != ? AND ' => 2,
12947
                    'ip.tool = ? AND ' => TOOL_FORUM,
12948
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12949
                    'f.lp_id = ?' => intval($this->lp_id),
12950
                ],
12951
            ],
12952
            'first'
12953
        );
12954
12955
        return $resultData['qty'] > 0;
12956
    }
12957
12958
    /**
12959
     * Get the forum for this learning path.
12960
     *
12961
     * @param int $sessionId
12962
     *
12963
     * @return bool
12964
     */
12965
    public function getForum($sessionId = 0)
12966
    {
12967
        $forumTable = Database::get_course_table(TABLE_FORUM);
12968
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12969
12970
        $fakeFrom = "$forumTable f
12971
            INNER JOIN $itemProperty ip ";
12972
12973
        if ($this->lp_session_id == 0) {
12974
            $fakeFrom .= "
12975
                ON (
12976
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12977
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12978
                    )
12979
                )
12980
            ";
12981
        } else {
12982
            $fakeFrom .= "
12983
                ON (
12984
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12985
                )
12986
            ";
12987
        }
12988
12989
        $resultData = Database::select(
12990
            'f.*',
12991
            $fakeFrom,
12992
            [
12993
                'where' => [
12994
                    'ip.visibility != ? AND ' => 2,
12995
                    'ip.tool = ? AND ' => TOOL_FORUM,
12996
                    'f.session_id = ? AND ' => $sessionId,
12997
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12998
                    'f.lp_id = ?' => intval($this->lp_id),
12999
                ],
13000
            ],
13001
            'first'
13002
        );
13003
13004
        if (empty($resultData)) {
13005
            return false;
13006
        }
13007
13008
        return $resultData;
13009
    }
13010
13011
    /**
13012
     * Create a forum for this learning path.
13013
     *
13014
     * @param int $forumCategoryId
13015
     *
13016
     * @return int The forum ID if was created. Otherwise return false
13017
     */
13018
    public function createForum($forumCategoryId)
13019
    {
13020
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
13021
13022
        $forumId = store_forum(
13023
            [
13024
                'lp_id' => $this->lp_id,
13025
                'forum_title' => $this->name,
13026
                'forum_comment' => null,
13027
                'forum_category' => (int) $forumCategoryId,
13028
                'students_can_edit_group' => ['students_can_edit' => 0],
13029
                'allow_new_threads_group' => ['allow_new_threads' => 0],
13030
                'default_view_type_group' => ['default_view_type' => 'flat'],
13031
                'group_forum' => 0,
13032
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
13033
            ],
13034
            [],
13035
            true
13036
        );
13037
13038
        return $forumId;
13039
    }
13040
13041
    /**
13042
     * Get the LP Final Item form.
13043
     *
13044
     * @throws Exception
13045
     * @throws HTML_QuickForm_Error
13046
     *
13047
     * @return string
13048
     */
13049
    public function getFinalItemForm()
13050
    {
13051
        $finalItem = $this->getFinalItem();
13052
        $title = '';
13053
13054
        if ($finalItem) {
13055
            $title = $finalItem->get_title();
13056
            $buttonText = get_lang('Save');
13057
            $content = $this->getSavedFinalItem();
13058
        } else {
13059
            $buttonText = get_lang('LPCreateDocument');
13060
            $content = $this->getFinalItemTemplate();
13061
        }
13062
13063
        $courseInfo = api_get_course_info();
13064
        $result = $this->generate_lp_folder($courseInfo);
13065
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
13066
        $relative_prefix = '../../';
13067
13068
        $editorConfig = [
13069
            'ToolbarSet' => 'LearningPathDocuments',
13070
            'Width' => '100%',
13071
            'Height' => '500',
13072
            'FullPage' => true,
13073
            'CreateDocumentDir' => $relative_prefix,
13074
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
13075
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
13076
        ];
13077
13078
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
13079
            'type' => 'document',
13080
            'lp_id' => $this->lp_id,
13081
        ]);
13082
13083
        $form = new FormValidator('final_item', 'POST', $url);
13084
        $form->addText('title', get_lang('Title'));
13085
        $form->addButtonSave($buttonText);
13086
        $form->addHtml(
13087
            Display::return_message(
13088
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
13089
                'normal',
13090
                false
13091
            )
13092
        );
13093
13094
        $renderer = $form->defaultRenderer();
13095
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
13096
13097
        $form->addHtmlEditor(
13098
            'content_lp_certificate',
13099
            null,
13100
            true,
13101
            false,
13102
            $editorConfig,
13103
            true
13104
        );
13105
        $form->addHidden('action', 'add_final_item');
13106
        $form->addHidden('path', Session::read('pathItem'));
13107
        $form->addHidden('previous', $this->get_last());
13108
        $form->setDefaults(
13109
            ['title' => $title, 'content_lp_certificate' => $content]
13110
        );
13111
13112
        if ($form->validate()) {
13113
            $values = $form->exportValues();
13114
            $lastItemId = $this->getLastInFirstLevel();
13115
13116
            if (!$finalItem) {
13117
                $documentId = $this->create_document(
13118
                    $this->course_info,
13119
                    $values['content_lp_certificate'],
13120
                    $values['title']
13121
                );
13122
                $this->add_item(
13123
                    0,
13124
                    $lastItemId,
13125
                    'final_item',
13126
                    $documentId,
13127
                    $values['title'],
13128
                    ''
13129
                );
13130
13131
                Display::addFlash(
13132
                    Display::return_message(get_lang('Added'))
13133
                );
13134
            } else {
13135
                $this->edit_document($this->course_info);
13136
            }
13137
        }
13138
13139
        return $form->returnForm();
13140
    }
13141
13142
    /**
13143
     * Check if the current lp item is first, both, last or none from lp list.
13144
     *
13145
     * @param int $currentItemId
13146
     *
13147
     * @return string
13148
     */
13149
    public function isFirstOrLastItem($currentItemId)
13150
    {
13151
        $lpItemId = [];
13152
        $typeListNotToVerify = self::getChapterTypes();
13153
13154
        // Using get_toc() function instead $this->items because returns the correct order of the items
13155
        foreach ($this->get_toc() as $item) {
13156
            if (!in_array($item['type'], $typeListNotToVerify)) {
13157
                $lpItemId[] = $item['id'];
13158
            }
13159
        }
13160
13161
        $lastLpItemIndex = count($lpItemId) - 1;
13162
        $position = array_search($currentItemId, $lpItemId);
13163
13164
        switch ($position) {
13165
            case 0:
13166
                if (!$lastLpItemIndex) {
13167
                    $answer = 'both';
13168
                    break;
13169
                }
13170
13171
                $answer = 'first';
13172
                break;
13173
            case $lastLpItemIndex:
13174
                $answer = 'last';
13175
                break;
13176
            default:
13177
                $answer = 'none';
13178
        }
13179
13180
        return $answer;
13181
    }
13182
13183
    /**
13184
     * Get whether this is a learning path with the accumulated SCORM time or not.
13185
     *
13186
     * @return int
13187
     */
13188
    public function getAccumulateScormTime()
13189
    {
13190
        return $this->accumulateScormTime;
13191
    }
13192
13193
    /**
13194
     * Set whether this is a learning path with the accumulated SCORM time or not.
13195
     *
13196
     * @param int $value (0 = false, 1 = true)
13197
     *
13198
     * @return bool Always returns true
13199
     */
13200
    public function setAccumulateScormTime($value)
13201
    {
13202
        $this->accumulateScormTime = (int) $value;
13203
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
13204
        $lp_id = $this->get_id();
13205
        $sql = "UPDATE $lp_table
13206
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
13207
                WHERE iid = $lp_id";
13208
        Database::query($sql);
13209
13210
        return true;
13211
    }
13212
13213
    /**
13214
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13215
     * the new learning path tool.
13216
     *
13217
     * The function is a big switch on tool type.
13218
     * In each case, we query the corresponding table for information and build the link
13219
     * with that information.
13220
     *
13221
     * @author Yannick Warnier <[email protected]> - rebranding based on
13222
     * previous work (display_addedresource_link_in_learnpath())
13223
     *
13224
     * @param int $course_id      Course code
13225
     * @param int $learningPathId The learning path ID (in lp table)
13226
     * @param int $id_in_path     the unique index in the items table
13227
     * @param int $lpViewId
13228
     * @param int $lpSessionId
13229
     *
13230
     * @return string
13231
     */
13232
    public static function rl_get_resource_link_for_learnpath(
13233
        $course_id,
13234
        $learningPathId,
13235
        $id_in_path,
13236
        $lpViewId,
13237
        $lpSessionId = 0
13238
    ) {
13239
        $session_id = api_get_session_id();
13240
        $course_info = api_get_course_info_by_id($course_id);
13241
13242
        $learningPathId = (int) $learningPathId;
13243
        $id_in_path = (int) $id_in_path;
13244
        $lpViewId = (int) $lpViewId;
13245
13246
        $em = Database::getManager();
13247
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13248
13249
        /** @var CLpItem $rowItem */
13250
        $rowItem = $lpItemRepo->findOneBy([
13251
            'cId' => $course_id,
13252
            'lpId' => $learningPathId,
13253
            'iid' => $id_in_path,
13254
        ]);
13255
13256
        if (!$rowItem) {
13257
            // Try one more time with "id"
13258
            /** @var CLpItem $rowItem */
13259
            $rowItem = $lpItemRepo->findOneBy([
13260
                'cId' => $course_id,
13261
                'lpId' => $learningPathId,
13262
                'id' => $id_in_path,
13263
            ]);
13264
13265
            if (!$rowItem) {
13266
                return -1;
13267
            }
13268
        }
13269
13270
        $course_code = $course_info['code'];
13271
        $type = $rowItem->getItemType();
13272
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13273
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13274
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13275
        $link = '';
13276
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13277
13278
        switch ($type) {
13279
            case 'dir':
13280
                return $main_dir_path.'lp/blank.php';
13281
            case TOOL_CALENDAR_EVENT:
13282
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13283
            case TOOL_ANNOUNCEMENT:
13284
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13285
            case TOOL_LINK:
13286
                $linkInfo = Link::getLinkInfo($id);
13287
                if ($linkInfo) {
13288
                    $itemPropertyInfo = api_get_item_property_info($course_id, TOOL_LINK, $id, $session_id);
13289
                    if ($itemPropertyInfo && 0 === (int) $itemPropertyInfo['visibility']) {
13290
                        return '';
13291
                    }
13292
                    if (isset($linkInfo['url'])) {
13293
                        return $linkInfo['url'];
13294
                    }
13295
                }
13296
13297
                return '';
13298
            case TOOL_QUIZ:
13299
                if (empty($id)) {
13300
                    return '';
13301
                }
13302
13303
                // Get the lp_item_view with the highest view_count.
13304
                $learnpathItemViewResult = $em
13305
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13306
                    ->findBy(
13307
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13308
                        ['viewCount' => 'DESC'],
13309
                        1
13310
                    );
13311
                /** @var CLpItemView $learnpathItemViewData */
13312
                $learnpathItemViewData = current($learnpathItemViewResult);
13313
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13314
13315
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13316
                    .http_build_query([
13317
                        'lp_init' => 1,
13318
                        'learnpath_item_view_id' => (int) $learnpathItemViewId,
13319
                        'learnpath_id' => (int) $learningPathId,
13320
                        'learnpath_item_id' => (int) $id_in_path,
13321
                        'exerciseId' => (int) $id,
13322
                    ]);
13323
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13324
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13325
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13326
                $myrow = Database::fetch_array($result);
13327
                $path = $myrow['path'];
13328
13329
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13330
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13331
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13332
            case TOOL_FORUM:
13333
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13334
            case TOOL_THREAD:
13335
                // forum post
13336
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13337
                if (empty($id)) {
13338
                    return '';
13339
                }
13340
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13341
                $result = Database::query($sql);
13342
                $myrow = Database::fetch_array($result);
13343
13344
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13345
                    .$extraParams;
13346
            case TOOL_POST:
13347
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13348
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13349
                $myrow = Database::fetch_array($result);
13350
13351
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13352
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13353
            case TOOL_READOUT_TEXT:
13354
                return api_get_path(WEB_CODE_PATH).
13355
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13356
            case TOOL_DOCUMENT:
13357
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13358
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13359
13360
                if (empty($document)) {
13361
                    // Try with normal id
13362
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13363
13364
                    if (empty($document)) {
13365
                        return '';
13366
                    }
13367
                }
13368
13369
                $documentPathInfo = pathinfo($document->getPath());
13370
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13371
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13372
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13373
13374
                $openmethod = 2;
13375
                $officedoc = false;
13376
                Session::write('openmethod', $openmethod);
13377
                Session::write('officedoc', $officedoc);
13378
13379
                // Same validation as in document/download.php
13380
                $isVisible = DocumentManager::is_visible(
13381
                    $document->getPath(),
13382
                    api_get_course_info(),
13383
                    api_get_session_id()
13384
                );
13385
13386
                if (!api_is_allowed_to_edit() && !$isVisible) {
13387
                    return '';
13388
                }
13389
13390
                if ($showDirectUrl) {
13391
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13392
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13393
                        if (Link::isPdfLink($file)) {
13394
                            return api_get_path(WEB_LIBRARY_PATH).
13395
                                'javascript/ViewerJS/index.html?zoom=page-width#'.$file;
13396
                        }
13397
                    }
13398
13399
                    return $file;
13400
                }
13401
13402
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13403
            case TOOL_LP_FINAL_ITEM:
13404
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13405
                    .$extraParams;
13406
            case 'assignments':
13407
                return $main_dir_path.'work/work.php?'.$extraParams;
13408
            case TOOL_DROPBOX:
13409
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13410
            case 'introduction_text': //DEPRECATED
13411
                return '';
13412
            case TOOL_COURSE_DESCRIPTION:
13413
                return $main_dir_path.'course_description?'.$extraParams;
13414
            case TOOL_GROUP:
13415
                return $main_dir_path.'group/group.php?'.$extraParams;
13416
            case TOOL_USER:
13417
                return $main_dir_path.'user/user.php?'.$extraParams;
13418
            case TOOL_STUDENTPUBLICATION:
13419
                if (!empty($rowItem->getPath())) {
13420
                    $workId = $rowItem->getPath();
13421
                    if (empty($lpSessionId) && !empty($session_id)) {
13422
                        // Check if a student publication with the same name exists in this session see BT#17700
13423
                        $title = Database::escape_string($rowItem->getTitle());
13424
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13425
                        $sql = "SELECT * FROM $table
13426
                                WHERE
13427
                                    active = 1 AND
13428
                                    parent_id = 0 AND
13429
                                    c_id = $course_id AND
13430
                                    session_id = $session_id AND
13431
                                    title = '$title'
13432
                                LIMIT 1";
13433
                        $result = Database::query($sql);
13434
                        if (Database::num_rows($result)) {
13435
                            $work = Database::fetch_array($result, 'ASSOC');
13436
                            if ($work) {
13437
                                $workId = $work['iid'];
13438
                            }
13439
                        }
13440
                    }
13441
13442
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13443
                }
13444
13445
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13446
        }
13447
13448
        return $link;
13449
    }
13450
13451
    /**
13452
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13453
     *
13454
     * @author Yannick Warnier <[email protected]>
13455
     *
13456
     * @param string $course_code    Course code
13457
     * @param int    $learningPathId
13458
     * @param int    $id_in_path     The resource ID
13459
     *
13460
     * @return string
13461
     */
13462
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13463
    {
13464
        $_course = api_get_course_info($course_code);
13465
        if (empty($_course)) {
13466
            return '';
13467
        }
13468
        $course_id = $_course['real_id'];
13469
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13470
        $learningPathId = (int) $learningPathId;
13471
        $id_in_path = (int) $id_in_path;
13472
13473
        $sql = "SELECT item_type, title, ref
13474
                FROM $tbl_lp_item
13475
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13476
        $res_item = Database::query($sql);
13477
13478
        if (Database::num_rows($res_item) < 1) {
13479
            return '';
13480
        }
13481
        $row_item = Database::fetch_array($res_item);
13482
        $type = strtolower($row_item['item_type']);
13483
        $id = $row_item['ref'];
13484
        $output = '';
13485
13486
        switch ($type) {
13487
            case TOOL_CALENDAR_EVENT:
13488
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13489
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13490
                $myrow = Database::fetch_array($result);
13491
                $output = $myrow['title'];
13492
                break;
13493
            case TOOL_ANNOUNCEMENT:
13494
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13495
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13496
                $myrow = Database::fetch_array($result);
13497
                $output = $myrow['title'];
13498
                break;
13499
            case TOOL_LINK:
13500
                // Doesn't take $target into account.
13501
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13502
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13503
                $myrow = Database::fetch_array($result);
13504
                $output = $myrow['title'];
13505
                break;
13506
            case TOOL_QUIZ:
13507
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13508
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13509
                $myrow = Database::fetch_array($result);
13510
                $output = $myrow['title'];
13511
                break;
13512
            case TOOL_FORUM:
13513
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13514
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13515
                $myrow = Database::fetch_array($result);
13516
                $output = $myrow['forum_name'];
13517
                break;
13518
            case TOOL_THREAD:
13519
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13520
                // Grabbing the title of the post.
13521
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13522
                $result_title = Database::query($sql_title);
13523
                $myrow_title = Database::fetch_array($result_title);
13524
                $output = $myrow_title['post_title'];
13525
                break;
13526
            case TOOL_POST:
13527
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13528
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13529
                $result = Database::query($sql);
13530
                $post = Database::fetch_array($result);
13531
                $output = $post['post_title'];
13532
                break;
13533
            case 'dir':
13534
            case TOOL_DOCUMENT:
13535
                $title = $row_item['title'];
13536
                $output = '-';
13537
                if (!empty($title)) {
13538
                    $output = $title;
13539
                }
13540
                break;
13541
            case 'hotpotatoes':
13542
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13543
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13544
                $myrow = Database::fetch_array($result);
13545
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13546
                $last = count($pathname) - 1; // Making a correct name for the link.
13547
                $filename = $pathname[$last]; // Making a correct name for the link.
13548
                $myrow['path'] = rawurlencode($myrow['path']);
13549
                $output = $filename;
13550
                break;
13551
        }
13552
13553
        return stripslashes($output);
13554
    }
13555
13556
    /**
13557
     * Get the parent names for the current item.
13558
     *
13559
     * @param int $newItemId Optional. The item ID
13560
     *
13561
     * @return array
13562
     */
13563
    public function getCurrentItemParentNames($newItemId = 0)
13564
    {
13565
        $newItemId = $newItemId ?: $this->get_current_item_id();
13566
        $return = [];
13567
        $item = $this->getItem($newItemId);
13568
        $parent = $this->getItem($item->get_parent());
13569
13570
        while ($parent) {
13571
            $return[] = $parent->get_title();
13572
            $parent = $this->getItem($parent->get_parent());
13573
        }
13574
13575
        return array_reverse($return);
13576
    }
13577
13578
    /**
13579
     * Reads and process "lp_subscription_settings" setting.
13580
     *
13581
     * @return array
13582
     */
13583
    public static function getSubscriptionSettings()
13584
    {
13585
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13586
        if (empty($subscriptionSettings)) {
13587
            // By default allow both settings
13588
            $subscriptionSettings = [
13589
                'allow_add_users_to_lp' => true,
13590
                'allow_add_users_to_lp_category' => true,
13591
            ];
13592
        } else {
13593
            $subscriptionSettings = $subscriptionSettings['options'];
13594
        }
13595
13596
        return $subscriptionSettings;
13597
    }
13598
13599
    /**
13600
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13601
     */
13602
    public function exportToCourseBuildFormat()
13603
    {
13604
        if (!api_is_allowed_to_edit()) {
13605
            return false;
13606
        }
13607
13608
        $courseBuilder = new CourseBuilder();
13609
        $itemList = [];
13610
        /** @var learnpathItem $item */
13611
        foreach ($this->items as $item) {
13612
            $itemList[$item->get_type()][] = $item->get_path();
13613
        }
13614
13615
        if (empty($itemList)) {
13616
            return false;
13617
        }
13618
13619
        if (isset($itemList['document'])) {
13620
            // Get parents
13621
            foreach ($itemList['document'] as $documentId) {
13622
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13623
                if (!empty($documentInfo['parents'])) {
13624
                    foreach ($documentInfo['parents'] as $parentInfo) {
13625
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13626
                            continue;
13627
                        }
13628
                        $itemList['document'][] = $parentInfo['iid'];
13629
                    }
13630
                }
13631
            }
13632
13633
            $courseInfo = api_get_course_info();
13634
            foreach ($itemList['document'] as $documentId) {
13635
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13636
                $items = DocumentManager::get_resources_from_source_html(
13637
                    $documentInfo['absolute_path'],
13638
                    true,
13639
                    TOOL_DOCUMENT
13640
                );
13641
13642
                if (!empty($items)) {
13643
                    foreach ($items as $item) {
13644
                        // Get information about source url
13645
                        $url = $item[0]; // url
13646
                        $scope = $item[1]; // scope (local, remote)
13647
                        $type = $item[2]; // type (rel, abs, url)
13648
13649
                        $origParseUrl = parse_url($url);
13650
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13651
13652
                        if ($scope == 'local') {
13653
                            if ($type == 'abs' || $type == 'rel') {
13654
                                $documentFile = strstr($realOrigPath, 'document');
13655
                                if (strpos($realOrigPath, $documentFile) !== false) {
13656
                                    $documentFile = str_replace('document', '', $documentFile);
13657
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13658
                                    // Document found! Add it to the list
13659
                                    if ($itemDocumentId) {
13660
                                        $itemList['document'][] = $itemDocumentId;
13661
                                    }
13662
                                }
13663
                            }
13664
                        }
13665
                    }
13666
                }
13667
            }
13668
13669
            $courseBuilder->build_documents(
13670
                api_get_session_id(),
13671
                $this->get_course_int_id(),
13672
                true,
13673
                $itemList['document']
13674
            );
13675
        }
13676
13677
        if (isset($itemList['quiz'])) {
13678
            $courseBuilder->build_quizzes(
13679
                api_get_session_id(),
13680
                $this->get_course_int_id(),
13681
                true,
13682
                $itemList['quiz']
13683
            );
13684
        }
13685
13686
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13687
13688
        /*if (!empty($itemList['thread'])) {
13689
            $postList = [];
13690
            foreach ($itemList['thread'] as $postId) {
13691
                $post = get_post_information($postId);
13692
                if ($post) {
13693
                    if (!isset($itemList['forum'])) {
13694
                        $itemList['forum'] = [];
13695
                    }
13696
                    $itemList['forum'][] = $post['forum_id'];
13697
                    $postList[] = $postId;
13698
                }
13699
            }
13700
13701
            if (!empty($postList)) {
13702
                $courseBuilder->build_forum_posts(
13703
                    $this->get_course_int_id(),
13704
                    null,
13705
                    null,
13706
                    $postList
13707
                );
13708
            }
13709
        }*/
13710
13711
        if (!empty($itemList['thread'])) {
13712
            $threadList = [];
13713
            $em = Database::getManager();
13714
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13715
            foreach ($itemList['thread'] as $threadId) {
13716
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13717
                $thread = $repo->find($threadId);
13718
                if ($thread) {
13719
                    $itemList['forum'][] = $thread->getForumId();
13720
                    $threadList[] = $thread->getIid();
13721
                }
13722
            }
13723
13724
            if (!empty($threadList)) {
13725
                $courseBuilder->build_forum_topics(
13726
                    api_get_session_id(),
13727
                    $this->get_course_int_id(),
13728
                    null,
13729
                    $threadList
13730
                );
13731
            }
13732
        }
13733
13734
        $forumCategoryList = [];
13735
        if (isset($itemList['forum'])) {
13736
            foreach ($itemList['forum'] as $forumId) {
13737
                $forumInfo = get_forums($forumId);
13738
                $forumCategoryList[] = $forumInfo['forum_category'];
13739
            }
13740
        }
13741
13742
        if (!empty($forumCategoryList)) {
13743
            $courseBuilder->build_forum_category(
13744
                api_get_session_id(),
13745
                $this->get_course_int_id(),
13746
                true,
13747
                $forumCategoryList
13748
            );
13749
        }
13750
13751
        if (!empty($itemList['forum'])) {
13752
            $courseBuilder->build_forums(
13753
                api_get_session_id(),
13754
                $this->get_course_int_id(),
13755
                true,
13756
                $itemList['forum']
13757
            );
13758
        }
13759
13760
        if (isset($itemList['link'])) {
13761
            $courseBuilder->build_links(
13762
                api_get_session_id(),
13763
                $this->get_course_int_id(),
13764
                true,
13765
                $itemList['link']
13766
            );
13767
        }
13768
13769
        if (!empty($itemList['student_publication'])) {
13770
            $courseBuilder->build_works(
13771
                api_get_session_id(),
13772
                $this->get_course_int_id(),
13773
                true,
13774
                $itemList['student_publication']
13775
            );
13776
        }
13777
13778
        $courseBuilder->build_learnpaths(
13779
            api_get_session_id(),
13780
            $this->get_course_int_id(),
13781
            true,
13782
            [$this->get_id()],
13783
            false
13784
        );
13785
13786
        $courseBuilder->restoreDocumentsFromList();
13787
13788
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13789
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13790
        $result = DocumentManager::file_send_for_download(
13791
            $zipPath,
13792
            true,
13793
            $this->get_name().'.zip'
13794
        );
13795
13796
        if ($result) {
13797
            api_not_allowed();
13798
        }
13799
13800
        return true;
13801
    }
13802
13803
    /**
13804
     * Get whether this is a learning path with the accumulated work time or not.
13805
     *
13806
     * @return int
13807
     */
13808
    public function getAccumulateWorkTime()
13809
    {
13810
        return (int) $this->accumulateWorkTime;
13811
    }
13812
13813
    /**
13814
     * Get whether this is a learning path with the accumulated work time or not.
13815
     *
13816
     * @return int
13817
     */
13818
    public function getAccumulateWorkTimeTotalCourse()
13819
    {
13820
        $table = Database::get_course_table(TABLE_LP_MAIN);
13821
        $sql = "SELECT SUM(accumulate_work_time) AS total
13822
                FROM $table
13823
                WHERE c_id = ".$this->course_int_id;
13824
        $result = Database::query($sql);
13825
        $row = Database::fetch_array($result);
13826
13827
        return (int) $row['total'];
13828
    }
13829
13830
    /**
13831
     * Set whether this is a learning path with the accumulated work time or not.
13832
     *
13833
     * @param int $value (0 = false, 1 = true)
13834
     *
13835
     * @return bool
13836
     */
13837
    public function setAccumulateWorkTime($value)
13838
    {
13839
        if (!api_get_configuration_value('lp_minimum_time')) {
13840
            return false;
13841
        }
13842
13843
        $this->accumulateWorkTime = (int) $value;
13844
        $table = Database::get_course_table(TABLE_LP_MAIN);
13845
        $lp_id = $this->get_id();
13846
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13847
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13848
        Database::query($sql);
13849
13850
        return true;
13851
    }
13852
13853
    /**
13854
     * @param int $lpId
13855
     * @param int $courseId
13856
     *
13857
     * @return mixed
13858
     */
13859
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13860
    {
13861
        $lpId = (int) $lpId;
13862
        $courseId = (int) $courseId;
13863
13864
        $table = Database::get_course_table(TABLE_LP_MAIN);
13865
        $sql = "SELECT accumulate_work_time
13866
                FROM $table
13867
                WHERE c_id = $courseId AND id = $lpId";
13868
        $result = Database::query($sql);
13869
        $row = Database::fetch_array($result);
13870
13871
        return $row['accumulate_work_time'];
13872
    }
13873
13874
    /**
13875
     * @param int $courseId
13876
     *
13877
     * @return int
13878
     */
13879
    public static function getAccumulateWorkTimeTotal($courseId)
13880
    {
13881
        $table = Database::get_course_table(TABLE_LP_MAIN);
13882
        $courseId = (int) $courseId;
13883
        $sql = "SELECT SUM(accumulate_work_time) AS total
13884
                FROM $table
13885
                WHERE c_id = $courseId";
13886
        $result = Database::query($sql);
13887
        $row = Database::fetch_array($result);
13888
13889
        return (int) $row['total'];
13890
    }
13891
13892
    /**
13893
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13894
     * and put the images in.
13895
     *
13896
     * @return array
13897
     */
13898
    public static function getIconSelect()
13899
    {
13900
        $theme = api_get_visual_theme();
13901
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13902
        $icons = ['' => get_lang('SelectAnOption')];
13903
13904
        if (is_dir($path)) {
13905
            $finder = new Finder();
13906
            $finder->files()->in($path);
13907
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13908
            /** @var SplFileInfo $file */
13909
            foreach ($finder as $file) {
13910
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13911
                    $icons[$file->getFilename()] = $file->getFilename();
13912
                }
13913
            }
13914
        }
13915
13916
        return $icons;
13917
    }
13918
13919
    /**
13920
     * @param int $lpId
13921
     *
13922
     * @return string
13923
     */
13924
    public static function getSelectedIcon($lpId)
13925
    {
13926
        $extraFieldValue = new ExtraFieldValue('lp');
13927
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13928
        $icon = '';
13929
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13930
            $icon = $lpIcon['value'];
13931
        }
13932
13933
        return $icon;
13934
    }
13935
13936
    /**
13937
     * @param int $lpId
13938
     *
13939
     * @return string
13940
     */
13941
    public static function getSelectedIconHtml($lpId)
13942
    {
13943
        $icon = self::getSelectedIcon($lpId);
13944
13945
        if (empty($icon)) {
13946
            return '';
13947
        }
13948
13949
        $theme = api_get_visual_theme();
13950
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13951
13952
        return Display::img($path);
13953
    }
13954
13955
    /**
13956
     * @param string $value
13957
     *
13958
     * @return string
13959
     */
13960
    public function cleanItemTitle($value)
13961
    {
13962
        $value = Security::remove_XSS(strip_tags($value));
13963
13964
        return $value;
13965
    }
13966
13967
    public function setItemTitle(FormValidator $form)
13968
    {
13969
        if (api_get_configuration_value('save_titles_as_html')) {
13970
            $form->addHtmlEditor(
13971
                'title',
13972
                get_lang('Title'),
13973
                true,
13974
                false,
13975
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13976
            );
13977
        } else {
13978
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13979
            $form->applyFilter('title', 'trim');
13980
            $form->applyFilter('title', 'html_filter');
13981
        }
13982
    }
13983
13984
    /**
13985
     * @return array
13986
     */
13987
    public function getItemsForForm($addParentCondition = false)
13988
    {
13989
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13990
        $course_id = api_get_course_int_id();
13991
13992
        $sql = "SELECT * FROM $tbl_lp_item
13993
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13994
13995
        if ($addParentCondition) {
13996
            $sql .= ' AND parent_item_id = 0 ';
13997
        }
13998
        $sql .= ' ORDER BY display_order ASC';
13999
14000
        $result = Database::query($sql);
14001
        $arrLP = [];
14002
        while ($row = Database::fetch_array($result)) {
14003
            $arrLP[] = [
14004
                'iid' => $row['iid'],
14005
                'id' => $row['iid'],
14006
                'item_type' => $row['item_type'],
14007
                'title' => $this->cleanItemTitle($row['title']),
14008
                'title_raw' => $row['title'],
14009
                'path' => $row['path'],
14010
                'description' => Security::remove_XSS($row['description']),
14011
                'parent_item_id' => $row['parent_item_id'],
14012
                'previous_item_id' => $row['previous_item_id'],
14013
                'next_item_id' => $row['next_item_id'],
14014
                'display_order' => $row['display_order'],
14015
                'max_score' => $row['max_score'],
14016
                'min_score' => $row['min_score'],
14017
                'mastery_score' => $row['mastery_score'],
14018
                'prerequisite' => $row['prerequisite'],
14019
                'max_time_allowed' => $row['max_time_allowed'],
14020
                'prerequisite_min_score' => $row['prerequisite_min_score'],
14021
                'prerequisite_max_score' => $row['prerequisite_max_score'],
14022
            ];
14023
        }
14024
14025
        return $arrLP;
14026
    }
14027
14028
    /**
14029
     * Gets whether this SCORM learning path has been marked to use the score
14030
     * as progress. Takes into account whether the learnpath matches (SCORM
14031
     * content + less than 2 items).
14032
     *
14033
     * @return bool True if the score should be used as progress, false otherwise
14034
     */
14035
    public function getUseScoreAsProgress()
14036
    {
14037
        // If not a SCORM, we don't care about the setting
14038
        if ($this->get_type() != 2) {
14039
            return false;
14040
        }
14041
        // If more than one step in the SCORM, we don't care about the setting
14042
        if ($this->get_total_items_count() > 1) {
14043
            return false;
14044
        }
14045
        $extraFieldValue = new ExtraFieldValue('lp');
14046
        $doUseScore = false;
14047
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
14048
        if (!empty($useScore) && isset($useScore['value'])) {
14049
            $doUseScore = $useScore['value'];
14050
        }
14051
14052
        return $doUseScore;
14053
    }
14054
14055
    /**
14056
     * Get the user identifier (user_id or username
14057
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
14058
     *
14059
     * @return string User ID or username, depending on configuration setting
14060
     */
14061
    public static function getUserIdentifierForExternalServices()
14062
    {
14063
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
14064
            return api_get_user_info(api_get_user_id())['username'];
14065
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
14066
            $extraFieldValue = new ExtraFieldValue('user');
14067
            $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'));
14068
14069
            return $extrafield['value'];
14070
        } else {
14071
            return api_get_user_id();
14072
        }
14073
    }
14074
14075
    /**
14076
     * Save the new order for learning path items.
14077
     *
14078
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
14079
     *
14080
     * @param array $orderList A associative array with item ID as key and parent ID as value.
14081
     * @param int   $courseId
14082
     */
14083
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
14084
    {
14085
        $courseId = $courseId ?: api_get_course_int_id();
14086
        $itemList = new LpItemOrderList();
14087
14088
        foreach ($orderList as $id => $parentId) {
14089
            $item = new LpOrderItem($id, $parentId);
14090
            $itemList->add($item);
14091
        }
14092
14093
        $parents = $itemList->getListOfParents();
14094
14095
        foreach ($parents as $parentId) {
14096
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
14097
            $previous_item_id = 0;
14098
            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...
14099
                $item_id = $sameParentLpItemList->list[$i]->id;
14100
                // display_order
14101
                $display_order = $i + 1;
14102
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
14103
                // previous_item_id
14104
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
14105
                $previous_item_id = $item_id;
14106
                // next_item_id
14107
                $next_item_id = 0;
14108
                if ($i < count($sameParentLpItemList->list) - 1) {
14109
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
14110
                }
14111
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
14112
            }
14113
        }
14114
14115
        $table = Database::get_course_table(TABLE_LP_ITEM);
14116
14117
        foreach ($itemList->list as $item) {
14118
            $params = [];
14119
            $params['display_order'] = $item->display_order;
14120
            $params['previous_item_id'] = $item->previous_item_id;
14121
            $params['next_item_id'] = $item->next_item_id;
14122
            $params['parent_item_id'] = $item->parent_item_id;
14123
14124
            Database::update(
14125
                $table,
14126
                $params,
14127
                [
14128
                    'iid = ? AND c_id = ? ' => [
14129
                        (int) $item->id,
14130
                        (int) $courseId,
14131
                    ],
14132
                ]
14133
            );
14134
        }
14135
    }
14136
14137
    /**
14138
     * Get the depth level of LP item.
14139
     *
14140
     * @param array $items
14141
     * @param int   $currentItemId
14142
     *
14143
     * @return int
14144
     */
14145
    private static function get_level_for_item($items, $currentItemId)
14146
    {
14147
        $parentItemId = 0;
14148
        if (isset($items[$currentItemId])) {
14149
            $parentItemId = $items[$currentItemId]->parent;
14150
        }
14151
14152
        if ($parentItemId == 0) {
14153
            return 0;
14154
        } else {
14155
            return self::get_level_for_item($items, $parentItemId) + 1;
14156
        }
14157
    }
14158
14159
    /**
14160
     * Generate the link for a learnpath category as course tool.
14161
     *
14162
     * @param int $categoryId
14163
     *
14164
     * @return string
14165
     */
14166
    private static function getCategoryLinkForTool($categoryId)
14167
    {
14168
        $categoryId = (int) $categoryId;
14169
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
14170
            .http_build_query(
14171
                [
14172
                    'action' => 'view_category',
14173
                    'id' => $categoryId,
14174
                ]
14175
            );
14176
14177
        return $link;
14178
    }
14179
14180
    /**
14181
     * Return the scorm item type object with spaces replaced with _
14182
     * The return result is use to build a css classname like scorm_type_$return.
14183
     *
14184
     * @param $in_type
14185
     *
14186
     * @return mixed
14187
     */
14188
    private static function format_scorm_type_item($in_type)
14189
    {
14190
        return str_replace(' ', '_', $in_type);
14191
    }
14192
14193
    /**
14194
     * Check and obtain the lp final item if exist.
14195
     *
14196
     * @return learnpathItem
14197
     */
14198
    private function getFinalItem()
14199
    {
14200
        if (empty($this->items)) {
14201
            return null;
14202
        }
14203
14204
        foreach ($this->items as $item) {
14205
            if ($item->type !== 'final_item') {
14206
                continue;
14207
            }
14208
14209
            return $item;
14210
        }
14211
    }
14212
14213
    /**
14214
     * Get the LP Final Item Template.
14215
     *
14216
     * @return string
14217
     */
14218
    private function getFinalItemTemplate()
14219
    {
14220
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
14221
    }
14222
14223
    /**
14224
     * Get the LP Final Item Url.
14225
     *
14226
     * @return string
14227
     */
14228
    private function getSavedFinalItem()
14229
    {
14230
        $finalItem = $this->getFinalItem();
14231
        $doc = DocumentManager::get_document_data_by_id(
14232
            $finalItem->path,
14233
            $this->cc
14234
        );
14235
        if ($doc && file_exists($doc['absolute_path'])) {
14236
            return file_get_contents($doc['absolute_path']);
14237
        }
14238
14239
        return '';
14240
    }
14241
14242
    /**
14243
     * Gets the form to evaluate if it exists contains the extra field extra_authorlpitem
14244
     * to establish authors when editing an item of an LP.
14245
     */
14246
    private function setAuthorLpItem(FormValidator $form)
14247
    {
14248
        if ($form->hasElement('extra_authorlpitem')) {
14249
            /** @var HTML_QuickForm_select $author */
14250
            $author = $form->getElement('extra_authorlpitem');
14251
            $options = [];
14252
            $field = new ExtraField('user');
14253
            $authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
14254
            $extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
14255
            if ($extraFieldId != 0) {
14256
                $extraFieldValueUser = new ExtraFieldValue('user');
14257
                $values = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
14258
                    $authorLp['variable'],
14259
                    1,
14260
                    true,
14261
                    false,
14262
                    true
14263
                );
14264
14265
                if (!empty($values)) {
14266
                    foreach ($values as $item) {
14267
                        $teacher = api_get_user_info($item['item_id']);
14268
                        $options[$teacher['id']] = $teacher['complete_name'];
14269
                    }
14270
                }
14271
            }
14272
            $author->setOptions($options);
14273
        }
14274
    }
14275
}
14276