Passed
Push — 1.11.x ( fbbc3d...d8fbfe )
by Julito
11:47
created

learnpath   F

Complexity

Total Complexity 1893

Size/Duplication

Total Lines 13976
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 7735
dl 0
loc 13976
rs 0.8
c 0
b 0
f 0
wmc 1893

225 Methods

Rating   Name   Duplication   Size   Complexity  
A get_maker() 0 7 2
A get_teacher_toc_buttons() 0 21 4
A get_name() 0 7 2
A get_course_int_id() 0 3 2
A set_course_int_id() 0 3 1
F __construct() 0 320 52
A getCourseCode() 0 3 1
B update_default_view_mode() 0 33 6
A switch_attempt_mode() 0 16 4
A tree_array() 0 4 1
A update_default_scorm_commit() 0 25 4
D stop_previous_item() 0 55 18
A sort_tree_array() 0 12 3
A set_seriousgame_mode() 0 23 4
A returnLpItemList() 0 15 2
B create_tree_array() 0 38 11
A set_attempt_mode() 0 32 5
A update_display_order() 0 29 5
B overview() 0 50 9
B print_recursive() 0 40 10
A update_scorm_debug() 0 23 4
A update_reinit() 0 23 4
B get_attempt_mode() 0 21 9
A return_new_tree() 0 34 4
F processBuildMenuElements() 0 441 53
F add_item() 0 234 16
A close() 0 13 2
F autocomplete_parents() 0 101 17
F add_lp() 0 145 14
A getProgressBar() 0 5 1
A next() 0 22 5
B get_iv_objectives_array() 0 38 6
A get_total_items_count() 0 3 1
A get_first_item_id() 0 8 2
A get_update_queue() 0 3 1
F get_link() 0 322 55
D prerequisites_match() 0 69 16
A previous() 0 11 1
B move_down() 0 54 8
A open() 0 9 1
F edit_item() 0 235 22
B getChildrenToc() 0 49 11
A get_author() 0 7 2
A get_last() 0 10 2
A getHideTableOfContents() 0 3 1
A categoryIsPublished() 0 24 2
A get_type() 0 8 3
A get_progress_bar() 0 12 1
C toggle_publish() 0 80 11
A get_type_static() 0 16 3
A get_items_status_list() 0 10 2
A get_theme() 0 7 2
A get_previous_index() 0 16 5
A get_preview_image() 0 7 2
C isBlockedByPrerequisite() 0 68 13
B get_progress_bar_text() 0 56 11
A get_js_lib() 0 8 2
B getTOCTree() 0 44 8
F first() 0 71 20
A get_items_details_as_js() 0 8 2
A get_progress_bar_mode() 0 7 2
A get_user_id() 0 7 2
A toggle_visibility() 0 14 2
A get_current_item_id() 0 8 2
A save_current() 0 32 6
A getLastInFirstLevel() 0 13 2
A getChapterTypes() 0 4 1
B get_preview_image_path() 0 28 7
B restart() 0 40 6
B get_iv_interactions_array() 0 54 8
A getNameNoTags() 0 3 1
C get_mediaplayer() 0 80 13
A getProgressFromLpList() 0 32 4
B move_up() 0 51 8
A get_interactions_count_from_db() 0 16 2
B get_scorm_prereq_string() 0 73 11
B get_scorm_xml_node() 0 19 7
A has_audio() 0 11 3
B edit_item_prereq() 0 40 7
A get_id() 0 7 2
A get_next_item_id() 0 10 3
B get_view() 0 49 7
F is_lp_visible_for_student() 0 139 26
A get_flat_ordered_items_list() 0 35 5
A getProgress() 0 23 2
A get_complete_items_count() 0 24 5
A get_objectives_count_from_db() 0 16 2
B delete_item() 0 70 6
D move_item() 0 125 18
A getTotalItemsCountWithoutDirs() 0 11 3
A toggleCategoryVisibility() 0 14 2
C getParentToc() 0 54 13
D getListArrayToc() 0 67 11
A get_view_id() 0 7 2
B toggleCategoryPublish() 0 86 9
C get_navigation_bar() 0 73 10
B get_next_index() 0 23 7
D getPackageType() 0 90 20
A get_common_index_terms_by_prefix() 0 17 3
A get_lp_session_id() 0 7 2
A get_previous_item_id() 0 5 1
A get_toc() 0 18 2
F categoryIsVisibleForStudent() 0 89 19
F delete() 0 120 19
A getStatusCSSClassName() 0 7 2
A delete_children_items() 0 22 4
B save_item() 0 47 9
A getCategory() 0 6 1
D display_edit_item() 0 121 21
A set_autolaunch() 0 27 2
F display_thread_form() 0 191 42
B getCalculateScore() 0 47 6
C save_last() 0 57 15
A copy() 0 26 1
A getCategoryFromCourseIntoSelect() 0 15 4
A getAccumulateScormTime() 0 3 1
F create_document() 0 169 30
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 getLpList() 0 9 1
A getCategorySessionId() 0 18 3
A set_error_msg() 0 9 3
B set_previous_step_as_prerequisite_for_all_items() 0 48 7
A updateCategory() 0 9 2
C start_current_item() 0 43 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 display_lp_prerequisites_list() 0 31 5
A set_prerequisite() 0 10 1
A set_modified_on() 0 10 1
F display_link_form() 0 174 34
F display_document_form() 0 351 73
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
B sortItemByOrderList() 0 48 7
A getCountCategories() 0 10 2
F createReadOutText() 0 135 27
A moveDownCategory() 0 9 2
F displayFrmReadOutText() 0 277 60
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 getItem() 0 7 3
A getCategoryId() 0 3 1
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 set_theme() 0 11 1
C rl_get_resource_name() 0 92 14
A getSavedFinalItem() 0 12 3
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 deleteCategory() 0 37 4
B generate_lp_folder() 0 57 8
A getAccumulateWorkTimeTotalCourse() 0 10 1
F display_quiz_form() 0 174 36
A moveUpCategory() 0 9 2
A set_proximity() 0 15 2
C display_item() 0 98 16
A set_expired_on() 0 23 3
F display_item_prerequisites_form() 0 182 21
A clear_prerequisites() 0 14 1
C scorm_export_to_pdf() 0 70 12
B isFirstOrLastItem() 0 32 6
B display_move_item() 0 51 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 78 6
A getFinalEvaluationItem() 0 12 3
A setItemTitle() 0 14 2
C build_action_menu() 0 165 11
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
F edit_document() 0 82 17
A getCurrentAttempt() 0 10 2
A delete_lp_image() 0 17 5
F display_forum_form() 0 178 40
A getUseScoreAsProgress() 0 18 5
A getFinalItem() 0 12 4
A setAccumulateScormTime() 0 11 1
A getFinalItemTemplate() 0 3 1
A get_extension() 0 5 1
F rl_get_resource_link_for_learnpath() 0 201 38
A getAccumulateWorkTimeTotal() 0 11 1
A getLpFromSession() 0 28 5
A generate_learning_path_folder() 0 28 4
A set_hide_toc_frame() 0 15 2
A getCategories() 0 9 1
A setCategoryId() 0 10 1
A getIconSelect() 0 19 4
F exportToCourseBuildFormat() 0 199 30
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 153 30
A setAccumulateWorkTime() 0 14 2
A set_name() 0 32 3

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
    const MAX_LP_ITEM_TITLE_LENGTH = 32;
36
    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 ($debug) {
123
            error_log('In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')');
124
        }
125
        if (empty($course)) {
126
            $course = api_get_course_id();
127
        }
128
        $course_info = api_get_course_info($course);
129
        if (!empty($course_info)) {
130
            $this->cc = $course_info['code'];
131
            $this->course_info = $course_info;
132
            $course_id = $course_info['real_id'];
133
        } else {
134
            $this->error = 'Course code does not exist in database.';
135
        }
136
137
        $lp_id = (int) $lp_id;
138
        $course_id = (int) $course_id;
139
        $this->set_course_int_id($course_id);
140
        // Check learnpath ID.
141
        if (empty($lp_id) || empty($course_id)) {
142
            $this->error = "Parameter is empty: LpId:'$lp_id', courseId: '$lp_id'";
143
        } else {
144
            // TODO: Make it flexible to use any course_code (still using env course code here).
145
            $lp_table = Database::get_course_table(TABLE_LP_MAIN);
146
            $sql = "SELECT * FROM $lp_table
147
                    WHERE iid = $lp_id";
148
            if ($debug) {
149
                error_log('learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0);
150
            }
151
            $res = Database::query($sql);
152
            if (Database::num_rows($res) > 0) {
153
                $this->lp_id = $lp_id;
154
                $row = Database::fetch_array($res);
155
                $this->type = $row['lp_type'];
156
                $this->name = stripslashes($row['name']);
157
                $this->proximity = $row['content_local'];
158
                $this->theme = $row['theme'];
159
                $this->maker = $row['content_maker'];
160
                $this->prevent_reinit = $row['prevent_reinit'];
161
                $this->seriousgame_mode = $row['seriousgame_mode'];
162
                $this->license = $row['content_license'];
163
                $this->scorm_debug = $row['debug'];
164
                $this->js_lib = $row['js_lib'];
165
                $this->path = $row['path'];
166
                $this->preview_image = $row['preview_image'];
167
                $this->author = $row['author'];
168
                $this->hide_toc_frame = $row['hide_toc_frame'];
169
                $this->lp_session_id = $row['session_id'];
170
                $this->use_max_score = $row['use_max_score'];
171
                $this->subscribeUsers = $row['subscribe_users'];
172
                $this->created_on = $row['created_on'];
173
                $this->modified_on = $row['modified_on'];
174
                $this->ref = $row['ref'];
175
                $this->categoryId = $row['category_id'];
176
                $this->accumulateScormTime = isset($row['accumulate_scorm_time']) ? $row['accumulate_scorm_time'] : 'true';
177
                $this->accumulateWorkTime = isset($row['accumulate_work_time']) ? $row['accumulate_work_time'] : 0;
178
179
                if (!empty($row['publicated_on'])) {
180
                    $this->publicated_on = $row['publicated_on'];
181
                }
182
183
                if (!empty($row['expired_on'])) {
184
                    $this->expired_on = $row['expired_on'];
185
                }
186
                if ($this->type == 2) {
187
                    if ($row['force_commit'] == 1) {
188
                        $this->force_commit = true;
189
                    }
190
                }
191
                $this->mode = $row['default_view_mod'];
192
193
                // Check user ID.
194
                if (empty($user_id)) {
195
                    $this->error = 'User ID is empty';
196
                } else {
197
                    $userInfo = api_get_user_info($user_id);
198
                    if (!empty($userInfo)) {
199
                        $this->user_id = $userInfo['user_id'];
200
                    } else {
201
                        $this->error = 'User ID does not exist in database #'.$user_id;
202
                    }
203
                }
204
205
                // End of variables checking.
206
                $session_id = api_get_session_id();
207
                //  Get the session condition for learning paths of the base + session.
208
                $session = api_get_session_condition($session_id);
209
                // Now get the latest attempt from this user on this LP, if available, otherwise create a new one.
210
                $lp_table = Database::get_course_table(TABLE_LP_VIEW);
211
212
                // Selecting by view_count descending allows to get the highest view_count first.
213
                $sql = "SELECT * FROM $lp_table
214
                        WHERE
215
                            c_id = $course_id AND
216
                            lp_id = $lp_id AND
217
                            user_id = $user_id
218
                            $session
219
                        ORDER BY view_count DESC";
220
                $res = Database::query($sql);
221
                if ($debug) {
222
                    error_log('learnpath::__construct() '.__LINE__.' - querying lp_view: '.$sql, 0);
223
                }
224
225
                if (Database::num_rows($res) > 0) {
226
                    if ($debug) {
227
                        error_log('learnpath::__construct() '.__LINE__.' - Found previous view');
228
                    }
229
                    $row = Database::fetch_array($res);
230
                    $this->attempt = $row['view_count'];
231
                    $this->lp_view_id = $row['id'];
232
                    $this->last_item_seen = $row['last_item'];
233
                    $this->progress_db = $row['progress'];
234
                    $this->lp_view_session_id = $row['session_id'];
235
                } elseif (!api_is_invitee()) {
236
                    if ($debug) {
237
                        error_log('learnpath::__construct() '.__LINE__.' - NOT Found previous view');
238
                    }
239
                    $this->attempt = 1;
240
                    $params = [
241
                        'c_id' => $course_id,
242
                        'lp_id' => $lp_id,
243
                        'user_id' => $user_id,
244
                        'view_count' => 1,
245
                        'session_id' => $session_id,
246
                        'last_item' => 0,
247
                    ];
248
                    $this->last_item_seen = 0;
249
                    $this->lp_view_session_id = $session_id;
250
                    $this->lp_view_id = Database::insert($lp_table, $params);
251
                    if (!empty($this->lp_view_id)) {
252
                        $sql = "UPDATE $lp_table SET id = iid
253
                                WHERE iid = ".$this->lp_view_id;
254
                        Database::query($sql);
255
                    }
256
                }
257
258
                // Initialise items.
259
                $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
260
                $sql = "SELECT * FROM $lp_item_table
261
                        WHERE c_id = $course_id AND lp_id = '".$this->lp_id."'
262
                        ORDER BY parent_item_id, display_order";
263
                $res = Database::query($sql);
264
265
                $lp_item_id_list = [];
266
                while ($row = Database::fetch_array($res)) {
267
                    $lp_item_id_list[] = $row['iid'];
268
                    switch ($this->type) {
269
                        case 3: //aicc
270
                            $oItem = new aiccItem('db', $row['iid'], $course_id);
271
                            if (is_object($oItem)) {
272
                                $my_item_id = $oItem->get_id();
273
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
274
                                $oItem->set_prevent_reinit($this->prevent_reinit);
275
                                // Don't use reference here as the next loop will make the pointed object change.
276
                                $this->items[$my_item_id] = $oItem;
277
                                $this->refs_list[$oItem->ref] = $my_item_id;
278
                                if ($debug) {
279
                                    error_log(
280
                                        'learnpath::__construct() - '.
281
                                        'aicc object with id '.$my_item_id.
282
                                        ' set in items[]',
283
                                        0
284
                                    );
285
                                }
286
                            }
287
                            break;
288
                        case 2:
289
                            $oItem = new scormItem('db', $row['iid'], $course_id);
290
                            if (is_object($oItem)) {
291
                                $my_item_id = $oItem->get_id();
292
                                $oItem->set_lp_view($this->lp_view_id, $course_id);
293
                                $oItem->set_prevent_reinit($this->prevent_reinit);
294
                                // Don't use reference here as the next loop will make the pointed object change.
295
                                $this->items[$my_item_id] = $oItem;
296
                                $this->refs_list[$oItem->ref] = $my_item_id;
297
                                if ($debug) {
298
                                    error_log('object with id '.$my_item_id.' set in items[]');
299
                                }
300
                            }
301
                            break;
302
                        case 1:
303
                        default:
304
                            if ($debug) {
305
                                error_log('learnpath::__construct() '.__LINE__.' - calling learnpathItem');
306
                            }
307
                            $oItem = new learnpathItem($row['iid'], $user_id, $course_id, $row);
308
309
                            if ($debug) {
310
                                error_log('learnpath::__construct() '.__LINE__.' - end calling learnpathItem');
311
                            }
312
                            if (is_object($oItem)) {
313
                                $my_item_id = $oItem->get_id();
314
                                // Moved down to when we are sure the item_view exists.
315
                                //$oItem->set_lp_view($this->lp_view_id);
316
                                $oItem->set_prevent_reinit($this->prevent_reinit);
317
                                // Don't use reference here as the next loop will make the pointed object change.
318
                                $this->items[$my_item_id] = $oItem;
319
                                $this->refs_list[$my_item_id] = $my_item_id;
320
                                if ($debug) {
321
                                    error_log(
322
                                        'learnpath::__construct() '.__LINE__.
323
                                        ' - object with id '.$my_item_id.' set in items[]'
324
                                    );
325
                                }
326
                            }
327
                            break;
328
                    }
329
330
                    // Setting the object level with variable $this->items[$i][parent]
331
                    foreach ($this->items as $itemLPObject) {
332
                        $level = self::get_level_for_item(
333
                            $this->items,
334
                            $itemLPObject->db_id
335
                        );
336
                        $itemLPObject->level = $level;
337
                    }
338
339
                    // Setting the view in the item object.
340
                    if (is_object($this->items[$row['iid']])) {
341
                        $this->items[$row['iid']]->set_lp_view($this->lp_view_id, $course_id);
342
                        if ($this->items[$row['iid']]->get_type() == TOOL_HOTPOTATOES) {
343
                            $this->items[$row['iid']]->current_start_time = 0;
344
                            $this->items[$row['iid']]->current_stop_time = 0;
345
                        }
346
                    }
347
                }
348
349
                if (!empty($lp_item_id_list)) {
350
                    $lp_item_id_list_to_string = implode("','", $lp_item_id_list);
351
                    if (!empty($lp_item_id_list_to_string)) {
352
                        // Get last viewing vars.
353
                        $itemViewTable = Database::get_course_table(TABLE_LP_ITEM_VIEW);
354
                        // This query should only return one or zero result.
355
                        $sql = "SELECT lp_item_id, status
356
                                FROM $itemViewTable
357
                                WHERE
358
                                    c_id = $course_id AND
359
                                    lp_view_id = ".$this->get_view_id()." AND
360
                                    lp_item_id IN ('".$lp_item_id_list_to_string."')
361
                                ORDER BY view_count DESC ";
362
                        $status_list = [];
363
                        $res = Database::query($sql);
364
                        while ($row = Database:: fetch_array($res)) {
365
                            $status_list[$row['lp_item_id']] = $row['status'];
366
                        }
367
368
                        foreach ($lp_item_id_list as $item_id) {
369
                            if (isset($status_list[$item_id])) {
370
                                $status = $status_list[$item_id];
371
                                if (is_object($this->items[$item_id])) {
372
                                    $this->items[$item_id]->set_status($status);
373
                                    if (empty($status)) {
374
                                        $this->items[$item_id]->set_status(
375
                                            $this->default_status
376
                                        );
377
                                    }
378
                                }
379
                            } else {
380
                                if (!api_is_invitee()) {
381
                                    if (is_object($this->items[$item_id])) {
382
                                        $this->items[$item_id]->set_status(
383
                                            $this->default_status
384
                                        );
385
                                    }
386
387
                                    if (!empty($this->lp_view_id)) {
388
                                        // Add that row to the lp_item_view table so that
389
                                        // we have something to show in the stats page.
390
                                        $params = [
391
                                            'c_id' => $course_id,
392
                                            'lp_item_id' => $item_id,
393
                                            'lp_view_id' => $this->lp_view_id,
394
                                            'view_count' => 1,
395
                                            'status' => 'not attempted',
396
                                            'start_time' => time(),
397
                                            'total_time' => 0,
398
                                            'score' => 0,
399
                                        ];
400
                                        $insertId = Database::insert($itemViewTable, $params);
401
402
                                        if ($insertId) {
403
                                            $sql = "UPDATE $itemViewTable SET id = iid
404
                                                    WHERE iid = $insertId";
405
                                            Database::query($sql);
406
                                        }
407
408
                                        $this->items[$item_id]->set_lp_view(
409
                                            $this->lp_view_id,
410
                                            $course_id
411
                                        );
412
                                    }
413
                                }
414
                            }
415
                        }
416
                    }
417
                }
418
419
                $this->ordered_items = self::get_flat_ordered_items_list(
420
                    $this->get_id(),
421
                    0,
422
                    $course_id
423
                );
424
                $this->max_ordered_items = 0;
425
                foreach ($this->ordered_items as $index => $dummy) {
426
                    if ($index > $this->max_ordered_items && !empty($dummy)) {
427
                        $this->max_ordered_items = $index;
428
                    }
429
                }
430
                // TODO: Define the current item better.
431
                $this->first();
432
                if ($debug) {
433
                    error_log('lp_view_session_id '.$this->lp_view_session_id);
434
                    error_log('End of learnpath constructor for learnpath '.$this->get_id());
435
                }
436
            } else {
437
                $this->error = 'Learnpath ID does not exist in database ('.$sql.')';
438
            }
439
        }
440
    }
441
442
    /**
443
     * @return string
444
     */
445
    public function getCourseCode()
446
    {
447
        return $this->cc;
448
    }
449
450
    /**
451
     * @return int
452
     */
453
    public function get_course_int_id()
454
    {
455
        return isset($this->course_int_id) ? $this->course_int_id : api_get_course_int_id();
456
    }
457
458
    /**
459
     * @param $course_id
460
     *
461
     * @return int
462
     */
463
    public function set_course_int_id($course_id)
464
    {
465
        return $this->course_int_id = (int) $course_id;
466
    }
467
468
    /**
469
     * Function rewritten based on old_add_item() from Yannick Warnier.
470
     * Due the fact that users can decide where the item should come, I had to overlook this function and
471
     * I found it better to rewrite it. Old function is still available.
472
     * Added also the possibility to add a description.
473
     *
474
     * @param int    $parent
475
     * @param int    $previous
476
     * @param string $type
477
     * @param int    $id               resource ID (ref)
478
     * @param string $title
479
     * @param string $description
480
     * @param int    $prerequisites
481
     * @param int    $max_time_allowed
482
     * @param int    $userId
483
     *
484
     * @return int
485
     */
486
    public function add_item(
487
        $parent,
488
        $previous,
489
        $type = 'dir',
490
        $id,
491
        $title,
492
        $description,
493
        $prerequisites = 0,
494
        $max_time_allowed = 0,
495
        $userId = 0
496
    ) {
497
        $course_id = $this->course_info['real_id'];
498
        if (empty($course_id)) {
499
            // Sometimes Oogie doesn't catch the course info but sets $this->cc
500
            $this->course_info = api_get_course_info($this->cc);
501
            $course_id = $this->course_info['real_id'];
502
        }
503
        $userId = empty($userId) ? api_get_user_id() : $userId;
504
        $sessionId = api_get_session_id();
505
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
506
        $_course = $this->course_info;
507
        $parent = (int) $parent;
508
        $previous = (int) $previous;
509
        $id = (int) $id;
510
        $max_time_allowed = htmlentities($max_time_allowed);
511
        if (empty($max_time_allowed)) {
512
            $max_time_allowed = 0;
513
        }
514
        $sql = "SELECT COUNT(iid) AS num
515
                FROM $tbl_lp_item
516
                WHERE
517
                    c_id = $course_id AND
518
                    lp_id = ".$this->get_id()." AND
519
                    parent_item_id = $parent ";
520
521
        $res_count = Database::query($sql);
522
        $row = Database::fetch_array($res_count);
523
        $num = $row['num'];
524
525
        $tmp_previous = 0;
526
        $display_order = 0;
527
        $next = 0;
528
        if ($num > 0) {
529
            if (empty($previous)) {
530
                $sql = "SELECT iid, next_item_id, display_order
531
                        FROM $tbl_lp_item
532
                        WHERE
533
                            c_id = $course_id AND
534
                            lp_id = ".$this->get_id()." AND
535
                            parent_item_id = $parent AND
536
                            previous_item_id = 0 OR
537
                            previous_item_id = $parent";
538
                $result = Database::query($sql);
539
                $row = Database::fetch_array($result);
540
                if ($row) {
541
                    $next = $row['iid'];
542
                }
543
            } else {
544
                $previous = (int) $previous;
545
                $sql = "SELECT iid, previous_item_id, next_item_id, display_order
546
						FROM $tbl_lp_item
547
                        WHERE
548
                            c_id = $course_id AND
549
                            lp_id = ".$this->get_id()." AND
550
                            id = $previous";
551
                $result = Database::query($sql);
552
                $row = Database::fetch_array($result);
553
                if ($row) {
554
                    $tmp_previous = $row['iid'];
555
                    $next = $row['next_item_id'];
556
                    $display_order = $row['display_order'];
557
                }
558
            }
559
        }
560
561
        $id = (int) $id;
562
        $typeCleaned = Database::escape_string($type);
563
        $max_score = 100;
564
        if ($type === 'quiz' && $id) {
565
            $sql = 'SELECT SUM(ponderation)
566
                    FROM '.Database::get_course_table(TABLE_QUIZ_QUESTION).' as quiz_question
567
                    INNER JOIN '.Database::get_course_table(TABLE_QUIZ_TEST_QUESTION).' as quiz_rel_question
568
                    ON
569
                        quiz_question.id = quiz_rel_question.question_id AND
570
                        quiz_question.c_id = quiz_rel_question.c_id
571
                    WHERE
572
                        quiz_rel_question.exercice_id = '.$id." AND
573
                        quiz_question.c_id = $course_id AND
574
                        quiz_rel_question.c_id = $course_id ";
575
            $rsQuiz = Database::query($sql);
576
            $max_score = Database::result($rsQuiz, 0, 0);
577
578
            // Disabling the exercise if we add it inside a LP
579
            $exercise = new Exercise($course_id);
580
            $exercise->read($id);
581
            $exercise->disable();
582
            $exercise->save();
583
        }
584
585
        $params = [
586
            'c_id' => $course_id,
587
            'lp_id' => $this->get_id(),
588
            'item_type' => $typeCleaned,
589
            'ref' => '',
590
            'title' => $title,
591
            'description' => $description,
592
            'path' => $id,
593
            'max_score' => $max_score,
594
            'parent_item_id' => $parent,
595
            'previous_item_id' => $previous,
596
            'next_item_id' => (int) $next,
597
            'display_order' => $display_order + 1,
598
            'prerequisite' => $prerequisites,
599
            'max_time_allowed' => $max_time_allowed,
600
            'min_score' => 0,
601
            'launch_data' => '',
602
        ];
603
604
        if ($prerequisites != 0) {
605
            $params['prerequisite'] = $prerequisites;
606
        }
607
608
        $new_item_id = Database::insert($tbl_lp_item, $params);
609
        if ($new_item_id) {
610
            $sql = "UPDATE $tbl_lp_item SET id = iid WHERE iid = $new_item_id";
611
            Database::query($sql);
612
613
            if (!empty($next)) {
614
                $sql = "UPDATE $tbl_lp_item
615
                        SET previous_item_id = $new_item_id
616
                        WHERE c_id = $course_id AND id = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
617
                Database::query($sql);
618
            }
619
620
            // Update the item that should be before the new item.
621
            if (!empty($tmp_previous)) {
622
                $sql = "UPDATE $tbl_lp_item
623
                        SET next_item_id = $new_item_id
624
                        WHERE c_id = $course_id AND id = $tmp_previous";
625
                Database::query($sql);
626
            }
627
628
            // Update all the items after the new item.
629
            $sql = "UPDATE $tbl_lp_item
630
                        SET display_order = display_order + 1
631
                    WHERE
632
                        c_id = $course_id AND
633
                        lp_id = ".$this->get_id()." AND
634
                        iid <> $new_item_id AND
635
                        parent_item_id = $parent AND
636
                        display_order > $display_order";
637
            Database::query($sql);
638
639
            // Update the item that should come after the new item.
640
            $sql = "UPDATE $tbl_lp_item
641
                    SET ref = $new_item_id
642
                    WHERE c_id = $course_id AND iid = $new_item_id";
643
            Database::query($sql);
644
645
            $sql = "UPDATE $tbl_lp_item
646
                    SET previous_item_id = ".$this->getLastInFirstLevel()."
647
                    WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
648
            Database::query($sql);
649
650
            // Upload audio.
651
            if (!empty($_FILES['mp3']['name'])) {
652
                // Create the audio folder if it does not exist yet.
653
                $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
654
                if (!is_dir($filepath.'audio')) {
655
                    mkdir(
656
                        $filepath.'audio',
657
                        api_get_permissions_for_new_directories()
658
                    );
659
                    $audio_id = add_document(
660
                        $_course,
661
                        '/audio',
662
                        'folder',
663
                        0,
664
                        'audio',
665
                        '',
666
                        0,
667
                        true,
668
                        null,
669
                        $sessionId,
670
                        $userId
671
                    );
672
                    api_item_property_update(
673
                        $_course,
674
                        TOOL_DOCUMENT,
675
                        $audio_id,
676
                        'FolderCreated',
677
                        $userId,
678
                        null,
679
                        null,
680
                        null,
681
                        null,
682
                        $sessionId
683
                    );
684
                    api_item_property_update(
685
                        $_course,
686
                        TOOL_DOCUMENT,
687
                        $audio_id,
688
                        'invisible',
689
                        $userId,
690
                        null,
691
                        null,
692
                        null,
693
                        null,
694
                        $sessionId
695
                    );
696
                }
697
698
                $file_path = handle_uploaded_document(
699
                    $_course,
700
                    $_FILES['mp3'],
701
                    api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
702
                    '/audio',
703
                    $userId,
704
                    '',
705
                    '',
706
                    '',
707
                    '',
708
                    false
709
                );
710
711
                // Store the mp3 file in the lp_item table.
712
                $sql = "UPDATE $tbl_lp_item SET
713
                          audio = '".Database::escape_string($file_path)."'
714
                        WHERE iid = '".intval($new_item_id)."'";
715
                Database::query($sql);
716
            }
717
        }
718
719
        return $new_item_id;
720
    }
721
722
    /**
723
     * Static admin function allowing addition of a learnpath to a course.
724
     *
725
     * @param string $courseCode
726
     * @param string $name
727
     * @param string $description
728
     * @param string $learnpath
729
     * @param string $origin
730
     * @param string $zipname       Zip file containing the learnpath or directory containing the learnpath
731
     * @param string $publicated_on
732
     * @param string $expired_on
733
     * @param int    $categoryId
734
     * @param int    $userId
735
     *
736
     * @return int The new learnpath ID on success, 0 on failure
737
     */
738
    public static function add_lp(
739
        $courseCode,
740
        $name,
741
        $description = '',
742
        $learnpath = 'guess',
743
        $origin = 'zip',
744
        $zipname = '',
745
        $publicated_on = '',
746
        $expired_on = '',
747
        $categoryId = 0,
748
        $userId = 0
749
    ) {
750
        global $charset;
751
752
        if (!empty($courseCode)) {
753
            $courseInfo = api_get_course_info($courseCode);
754
            $course_id = $courseInfo['real_id'];
755
        } else {
756
            $course_id = api_get_course_int_id();
757
            $courseInfo = api_get_course_info();
758
        }
759
760
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
761
        // Check course code exists.
762
        // Check lp_name doesn't exist, otherwise append something.
763
        $i = 0;
764
        $categoryId = (int) $categoryId;
765
        // Session id.
766
        $session_id = api_get_session_id();
767
        $userId = empty($userId) ? api_get_user_id() : $userId;
768
769
        if (empty($publicated_on)) {
770
            $publicated_on = null;
771
        } else {
772
            $publicated_on = Database::escape_string(api_get_utc_datetime($publicated_on));
773
        }
774
775
        if (empty($expired_on)) {
776
            $expired_on = null;
777
        } else {
778
            $expired_on = Database::escape_string(api_get_utc_datetime($expired_on));
779
        }
780
781
        $check_name = "SELECT * FROM $tbl_lp
782
                       WHERE c_id = $course_id AND name = '".Database::escape_string($name)."'";
783
        $res_name = Database::query($check_name);
784
785
        while (Database::num_rows($res_name)) {
786
            // There is already one such name, update the current one a bit.
787
            $i++;
788
            $name = $name.' - '.$i;
789
            $check_name = "SELECT * FROM $tbl_lp
790
                           WHERE c_id = $course_id AND name = '".Database::escape_string($name)."' ";
791
            $res_name = Database::query($check_name);
792
        }
793
        // New name does not exist yet; keep it.
794
        // Escape description.
795
        // Kevin: added htmlentities().
796
        $description = Database::escape_string(api_htmlentities($description, ENT_QUOTES, $charset));
797
        $type = 1;
798
        switch ($learnpath) {
799
            case 'guess':
800
                break;
801
            case 'dokeos':
802
            case 'chamilo':
803
                $type = 1;
804
                break;
805
            case 'aicc':
806
                break;
807
        }
808
809
        switch ($origin) {
810
            case 'zip':
811
                // Check zip name string. If empty, we are currently creating a new Chamilo learnpath.
812
                break;
813
            case 'manual':
814
            default:
815
                $get_max = "SELECT MAX(display_order)
816
                            FROM $tbl_lp WHERE c_id = $course_id";
817
                $res_max = Database::query($get_max);
818
                if (Database::num_rows($res_max) < 1) {
819
                    $dsp = 1;
820
                } else {
821
                    $row = Database::fetch_array($res_max);
822
                    $dsp = $row[0] + 1;
823
                }
824
825
                $params = [
826
                    'c_id' => $course_id,
827
                    'lp_type' => $type,
828
                    'name' => $name,
829
                    'description' => $description,
830
                    'path' => '',
831
                    'default_view_mod' => 'embedded',
832
                    'default_encoding' => 'UTF-8',
833
                    'display_order' => $dsp,
834
                    'content_maker' => 'Chamilo',
835
                    'content_local' => 'local',
836
                    'js_lib' => '',
837
                    'session_id' => $session_id,
838
                    'created_on' => api_get_utc_datetime(),
839
                    'modified_on' => api_get_utc_datetime(),
840
                    'publicated_on' => $publicated_on,
841
                    'expired_on' => $expired_on,
842
                    'category_id' => $categoryId,
843
                    'force_commit' => 0,
844
                    'content_license' => '',
845
                    'debug' => 0,
846
                    'theme' => '',
847
                    'preview_image' => '',
848
                    'author' => '',
849
                    'prerequisite' => 0,
850
                    'hide_toc_frame' => 0,
851
                    'seriousgame_mode' => 0,
852
                    'autolaunch' => 0,
853
                    'max_attempts' => 0,
854
                    'subscribe_users' => 0,
855
                    'accumulate_scorm_time' => 1,
856
                ];
857
                $id = Database::insert($tbl_lp, $params);
858
859
                if ($id > 0) {
860
                    $sql = "UPDATE $tbl_lp SET id = iid WHERE iid = $id";
861
                    Database::query($sql);
862
863
                    // Insert into item_property.
864
                    api_item_property_update(
865
                        $courseInfo,
866
                        TOOL_LEARNPATH,
867
                        $id,
868
                        'LearnpathAdded',
869
                        $userId
870
                    );
871
                    api_set_default_visibility(
872
                        $id,
873
                        TOOL_LEARNPATH,
874
                        0,
875
                        $courseInfo,
876
                        $session_id,
877
                        $userId
878
                    );
879
880
                    return $id;
881
                }
882
                break;
883
        }
884
    }
885
886
    /**
887
     * Auto completes the parents of an item in case it's been completed or passed.
888
     *
889
     * @param int $item Optional ID of the item from which to look for parents
890
     */
891
    public function autocomplete_parents($item)
892
    {
893
        $debug = $this->debug;
894
895
        if (empty($item)) {
896
            $item = $this->current;
897
        }
898
899
        $currentItem = $this->getItem($item);
900
        if ($currentItem) {
901
            $parent_id = $currentItem->get_parent();
902
            $parent = $this->getItem($parent_id);
903
            if ($parent) {
904
                // if $item points to an object and there is a parent.
905
                if ($debug) {
906
                    error_log(
907
                        'Autocompleting parent of item '.$item.' '.
908
                        $currentItem->get_title().'" (item '.$parent_id.' "'.$parent->get_title().'") ',
909
                        0
910
                    );
911
                }
912
913
                // New experiment including failed and browsed in completed status.
914
                //$current_status = $currentItem->get_status();
915
                //if ($currentItem->is_done() || $current_status == 'browsed' || $current_status == 'failed') {
916
                // Fixes chapter auto complete
917
                if (true) {
918
                    // If the current item is completed or passes or succeeded.
919
                    $updateParentStatus = true;
920
                    if ($debug) {
921
                        error_log('Status of current item is alright');
922
                    }
923
924
                    foreach ($parent->get_children() as $childItemId) {
925
                        $childItem = $this->getItem($childItemId);
926
927
                        // If children was not set try to get the info
928
                        if (empty($childItem->db_item_view_id)) {
929
                            $childItem->set_lp_view($this->lp_view_id, $this->course_int_id);
930
                        }
931
932
                        // Check all his brothers (parent's children) for completion status.
933
                        if ($childItemId != $item) {
934
                            if ($debug) {
935
                                error_log(
936
                                    'Looking at brother #'.$childItemId.' "'.$childItem->get_title().'", status is '.$childItem->get_status(),
937
                                    0
938
                                );
939
                            }
940
                            // Trying completing parents of failed and browsed items as well.
941
                            if ($childItem->status_is(
942
                                [
943
                                    'completed',
944
                                    'passed',
945
                                    'succeeded',
946
                                    'browsed',
947
                                    'failed',
948
                                ]
949
                            )
950
                            ) {
951
                                // Keep completion status to true.
952
                                continue;
953
                            } else {
954
                                if ($debug > 2) {
955
                                    error_log(
956
                                        '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,
957
                                        0
958
                                    );
959
                                }
960
                                $updateParentStatus = false;
961
                                break;
962
                            }
963
                        }
964
                    }
965
966
                    if ($updateParentStatus) {
967
                        // If all the children were completed:
968
                        $parent->set_status('completed');
969
                        $parent->save(false, $this->prerequisites_match($parent->get_id()));
970
                        // Force the status to "completed"
971
                        //$this->update_queue[$parent->get_id()] = $parent->get_status();
972
                        $this->update_queue[$parent->get_id()] = 'completed';
973
                        if ($debug) {
974
                            error_log(
975
                                'Added parent #'.$parent->get_id().' "'.$parent->get_title().'" to update queue status: completed '.
976
                                print_r($this->update_queue, 1),
977
                                0
978
                            );
979
                        }
980
                        // Recursive call.
981
                        $this->autocomplete_parents($parent->get_id());
982
                    }
983
                }
984
            } else {
985
                if ($debug) {
986
                    error_log("Parent #$parent_id does not exists");
987
                }
988
            }
989
        } else {
990
            if ($debug) {
991
                error_log("#$item is an item that doesn't have parents");
992
            }
993
        }
994
    }
995
996
    /**
997
     * Closes the current resource.
998
     *
999
     * Stops the timer
1000
     * Saves into the database if required
1001
     * Clears the current resource data from this object
1002
     *
1003
     * @return bool True on success, false on failure
1004
     */
1005
    public function close()
1006
    {
1007
        if (empty($this->lp_id)) {
1008
            $this->error = 'Trying to close this learnpath but no ID is set';
1009
1010
            return false;
1011
        }
1012
        $this->current_time_stop = time();
1013
        $this->ordered_items = [];
1014
        $this->index = 0;
1015
        unset($this->lp_id);
1016
        //unset other stuff
1017
        return true;
1018
    }
1019
1020
    /**
1021
     * Static admin function allowing removal of a learnpath.
1022
     *
1023
     * @param array  $courseInfo
1024
     * @param int    $id         Learnpath ID
1025
     * @param string $delete     Whether to delete data or keep it (default: 'keep', others: 'remove')
1026
     *
1027
     * @return bool True on success, false on failure (might change that to return number of elements deleted)
1028
     */
1029
    public function delete($courseInfo = null, $id = null, $delete = 'keep')
1030
    {
1031
        $course_id = api_get_course_int_id();
1032
        if (!empty($courseInfo)) {
1033
            $course_id = isset($courseInfo['real_id']) ? $courseInfo['real_id'] : $course_id;
1034
        }
1035
1036
        // TODO: Implement a way of getting this to work when the current object is not set.
1037
        // In clear: implement this in the item class as well (abstract class) and use the given ID in queries.
1038
        // If an ID is specifically given and the current LP is not the same, prevent delete.
1039
        if (!empty($id) && ($id != $this->lp_id)) {
1040
            return false;
1041
        }
1042
1043
        $lp = Database::get_course_table(TABLE_LP_MAIN);
1044
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1045
        $lp_view = Database::get_course_table(TABLE_LP_VIEW);
1046
        $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
1047
1048
        // Delete lp item id.
1049
        foreach ($this->items as $lpItemId => $dummy) {
1050
            $sql = "DELETE FROM $lp_item_view
1051
                    WHERE c_id = $course_id AND lp_item_id = '".$lpItemId."'";
1052
            Database::query($sql);
1053
        }
1054
1055
        // Proposed by Christophe (nickname: clefevre)
1056
        $sql = "DELETE FROM $lp_item
1057
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1058
        Database::query($sql);
1059
1060
        $sql = "DELETE FROM $lp_view
1061
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
1062
        Database::query($sql);
1063
1064
        self::toggle_publish($this->lp_id, 'i');
1065
1066
        if ($this->type == 2 || $this->type == 3) {
1067
            // This is a scorm learning path, delete the files as well.
1068
            $sql = "SELECT path FROM $lp
1069
                    WHERE iid = ".$this->lp_id;
1070
            $res = Database::query($sql);
1071
            if (Database::num_rows($res) > 0) {
1072
                $row = Database::fetch_array($res);
1073
                $path = $row['path'];
1074
                $sql = "SELECT id FROM $lp
1075
                        WHERE
1076
                            c_id = $course_id AND
1077
                            path = '$path' AND
1078
                            iid != ".$this->lp_id;
1079
                $res = Database::query($sql);
1080
                if (Database::num_rows($res) > 0) {
1081
                    // Another learning path uses this directory, so don't delete it.
1082
                    if ($this->debug > 2) {
1083
                        error_log('In learnpath::delete(), found other LP using path '.$path.', keeping directory', 0);
1084
                    }
1085
                } else {
1086
                    // No other LP uses that directory, delete it.
1087
                    $course_rel_dir = api_get_course_path().'/scorm/'; // scorm dir web path starting from /courses
1088
                    // The absolute system path for this course.
1089
                    $course_scorm_dir = api_get_path(SYS_COURSE_PATH).$course_rel_dir;
1090
                    if ($delete == 'remove' && is_dir($course_scorm_dir.$path) && !empty($course_scorm_dir)) {
1091
                        if ($this->debug > 2) {
1092
                            error_log('In learnpath::delete(), found SCORM, deleting directory: '.$course_scorm_dir.$path, 0);
1093
                        }
1094
                        // Proposed by Christophe (clefevre).
1095
                        if (strcmp(substr($path, -2), "/.") == 0) {
1096
                            $path = substr($path, 0, -1); // Remove "." at the end.
1097
                        }
1098
                        //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way.
1099
                        rmdirr($course_scorm_dir.$path);
1100
                    }
1101
                }
1102
            }
1103
        }
1104
1105
        $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
1106
        $link = 'lp/lp_controller.php?action=view&lp_id='.$this->lp_id;
1107
        // Delete tools
1108
        $sql = "DELETE FROM $tbl_tool
1109
                WHERE c_id = $course_id AND (link LIKE '$link%' AND image='scormbuilder.gif')";
1110
        Database::query($sql);
1111
1112
        if (api_get_configuration_value('allow_lp_subscription_to_usergroups')) {
1113
            $table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
1114
            $sql = "DELETE FROM $table
1115
                    WHERE
1116
                        lp_id = {$this->lp_id} AND
1117
                        c_id = $course_id ";
1118
            Database::query($sql);
1119
        }
1120
1121
        $sql = "DELETE FROM $lp
1122
                WHERE iid = ".$this->lp_id;
1123
        Database::query($sql);
1124
        // Updates the display order of all lps.
1125
        $this->update_display_order();
1126
1127
        api_item_property_update(
1128
            api_get_course_info(),
1129
            TOOL_LEARNPATH,
1130
            $this->lp_id,
1131
            'delete',
1132
            api_get_user_id()
1133
        );
1134
1135
        $link_info = GradebookUtils::isResourceInCourseGradebook(
1136
            api_get_course_id(),
1137
            4,
1138
            $id,
1139
            api_get_session_id()
1140
        );
1141
1142
        if ($link_info !== false) {
1143
            GradebookUtils::remove_resource_from_course_gradebook($link_info['id']);
1144
        }
1145
1146
        if (api_get_setting('search_enabled') == 'true') {
1147
            require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1148
            delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id);
1149
        }
1150
    }
1151
1152
    /**
1153
     * Removes all the children of one item - dangerous!
1154
     *
1155
     * @param int $id Element ID of which children have to be removed
1156
     *
1157
     * @return int Total number of children removed
1158
     */
1159
    public function delete_children_items($id)
1160
    {
1161
        $course_id = $this->course_info['real_id'];
1162
1163
        $num = 0;
1164
        $id = (int) $id;
1165
        if (empty($id) || empty($course_id)) {
1166
            return false;
1167
        }
1168
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1169
        $sql = "SELECT * FROM $lp_item
1170
                WHERE c_id = $course_id AND parent_item_id = $id";
1171
        $res = Database::query($sql);
1172
        while ($row = Database::fetch_array($res)) {
1173
            $num += $this->delete_children_items($row['iid']);
1174
            $sql = "DELETE FROM $lp_item
1175
                    WHERE c_id = $course_id AND iid = ".$row['iid'];
1176
            Database::query($sql);
1177
            $num++;
1178
        }
1179
1180
        return $num;
1181
    }
1182
1183
    /**
1184
     * Removes an item from the current learnpath.
1185
     *
1186
     * @param int $id Elem ID (0 if first)
1187
     *
1188
     * @return int Number of elements moved
1189
     *
1190
     * @todo implement resource removal
1191
     */
1192
    public function delete_item($id)
1193
    {
1194
        $course_id = api_get_course_int_id();
1195
        $id = (int) $id;
1196
        // TODO: Implement the resource removal.
1197
        if (empty($id) || empty($course_id)) {
1198
            return false;
1199
        }
1200
        // First select item to get previous, next, and display order.
1201
        $lp_item = Database::get_course_table(TABLE_LP_ITEM);
1202
        $sql_sel = "SELECT * FROM $lp_item WHERE iid = $id";
1203
        $res_sel = Database::query($sql_sel);
1204
        if (Database::num_rows($res_sel) < 1) {
1205
            return false;
1206
        }
1207
        $row = Database::fetch_array($res_sel);
1208
        $previous = $row['previous_item_id'];
1209
        $next = $row['next_item_id'];
1210
        $display = $row['display_order'];
1211
        $parent = $row['parent_item_id'];
1212
        $lp = $row['lp_id'];
1213
        // Delete children items.
1214
        $this->delete_children_items($id);
1215
        // Now delete the item.
1216
        $sql_del = "DELETE FROM $lp_item WHERE iid = $id";
1217
        Database::query($sql_del);
1218
        // Now update surrounding items.
1219
        $sql_upd = "UPDATE $lp_item SET next_item_id = $next
1220
                    WHERE iid = $previous";
1221
        Database::query($sql_upd);
1222
        $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous
1223
                    WHERE iid = $next AND item_type != '".TOOL_LP_FINAL_ITEM."'";
1224
        Database::query($sql_upd);
1225
        // Now update all following items with new display order.
1226
        $sql_all = "UPDATE $lp_item SET display_order = display_order-1
1227
                    WHERE
1228
                        c_id = $course_id AND
1229
                        lp_id = $lp AND
1230
                        parent_item_id = $parent AND
1231
                        display_order > $display";
1232
        Database::query($sql_all);
1233
1234
        //Removing prerequisites since the item will not longer exist
1235
        $sql_all = "UPDATE $lp_item SET prerequisite = ''
1236
                    WHERE c_id = $course_id AND prerequisite = $id";
1237
        Database::query($sql_all);
1238
1239
        $sql = "UPDATE $lp_item
1240
                SET previous_item_id = ".$this->getLastInFirstLevel()."
1241
                WHERE c_id = $course_id AND lp_id = {$this->lp_id} AND item_type = '".TOOL_LP_FINAL_ITEM."'";
1242
        Database::query($sql);
1243
1244
        // Remove from search engine if enabled.
1245
        if (api_get_setting('search_enabled') === 'true') {
1246
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
1247
            $sql = 'SELECT * FROM %s
1248
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1249
                    LIMIT 1';
1250
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1251
            $res = Database::query($sql);
1252
            if (Database::num_rows($res) > 0) {
1253
                $row2 = Database::fetch_array($res);
1254
                $di = new ChamiloIndexer();
1255
                $di->remove_document($row2['search_did']);
1256
            }
1257
            $sql = 'DELETE FROM %s
1258
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
1259
                    LIMIT 1';
1260
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id);
1261
            Database::query($sql);
1262
        }
1263
    }
1264
1265
    /**
1266
     * Updates an item's content in place.
1267
     *
1268
     * @param int    $id               Element ID
1269
     * @param int    $parent           Parent item ID
1270
     * @param int    $previous         Previous item ID
1271
     * @param string $title            Item title
1272
     * @param string $description      Item description
1273
     * @param string $prerequisites    Prerequisites (optional)
1274
     * @param array  $audio            The array resulting of the $_FILES[mp3] element
1275
     * @param int    $max_time_allowed
1276
     * @param string $url
1277
     *
1278
     * @return bool True on success, false on error
1279
     */
1280
    public function edit_item(
1281
        $id,
1282
        $parent,
1283
        $previous,
1284
        $title,
1285
        $description,
1286
        $prerequisites = '0',
1287
        $audio = [],
1288
        $max_time_allowed = 0,
1289
        $url = ''
1290
    ) {
1291
        $course_id = api_get_course_int_id();
1292
        $_course = api_get_course_info();
1293
        $id = (int) $id;
1294
1295
        if (empty($max_time_allowed)) {
1296
            $max_time_allowed = 0;
1297
        }
1298
1299
        if (empty($id) || empty($_course)) {
1300
            return false;
1301
        }
1302
1303
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
1304
        $sql = "SELECT * FROM $tbl_lp_item
1305
                WHERE iid = $id";
1306
        $res_select = Database::query($sql);
1307
        $row_select = Database::fetch_array($res_select);
1308
        $audio_update_sql = '';
1309
        if (is_array($audio) && !empty($audio['tmp_name']) && $audio['error'] === 0) {
1310
            // Create the audio folder if it does not exist yet.
1311
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
1312
            if (!is_dir($filepath.'audio')) {
1313
                mkdir($filepath.'audio', api_get_permissions_for_new_directories());
1314
                $audio_id = add_document(
1315
                    $_course,
1316
                    '/audio',
1317
                    'folder',
1318
                    0,
1319
                    'audio'
1320
                );
1321
                api_item_property_update(
1322
                    $_course,
1323
                    TOOL_DOCUMENT,
1324
                    $audio_id,
1325
                    'FolderCreated',
1326
                    api_get_user_id(),
1327
                    null,
1328
                    null,
1329
                    null,
1330
                    null,
1331
                    api_get_session_id()
1332
                );
1333
                api_item_property_update(
1334
                    $_course,
1335
                    TOOL_DOCUMENT,
1336
                    $audio_id,
1337
                    'invisible',
1338
                    api_get_user_id(),
1339
                    null,
1340
                    null,
1341
                    null,
1342
                    null,
1343
                    api_get_session_id()
1344
                );
1345
            }
1346
1347
            // Upload file in documents.
1348
            $pi = pathinfo($audio['name']);
1349
            if ($pi['extension'] === 'mp3') {
1350
                $c_det = api_get_course_info($this->cc);
1351
                $bp = api_get_path(SYS_COURSE_PATH).$c_det['path'].'/document';
1352
                $path = handle_uploaded_document(
1353
                    $c_det,
1354
                    $audio,
1355
                    $bp,
1356
                    '/audio',
1357
                    api_get_user_id(),
1358
                    0,
1359
                    null,
1360
                    0,
1361
                    'rename',
1362
                    false,
1363
                    0
1364
                );
1365
                // Update reference in lp_item - audio path is the path from inside de document/audio/ dir.
1366
                $audio_update_sql = ", audio = '".Database::escape_string($path)."' ";
1367
            }
1368
        }
1369
1370
        $same_parent = $row_select['parent_item_id'] == $parent ? true : false;
1371
        $same_previous = $row_select['previous_item_id'] == $previous ? true : false;
1372
1373
        // TODO: htmlspecialchars to be checked for encoding related problems.
1374
        if ($same_parent && $same_previous) {
1375
            // Only update title and description.
1376
            $sql = "UPDATE $tbl_lp_item
1377
                    SET title = '".Database::escape_string($title)."',
1378
                        prerequisite = '".$prerequisites."',
1379
                        description = '".Database::escape_string($description)."'
1380
                        ".$audio_update_sql.",
1381
                        max_time_allowed = '".Database::escape_string($max_time_allowed)."'
1382
                    WHERE iid = $id";
1383
            Database::query($sql);
1384
        } else {
1385
            $old_parent = $row_select['parent_item_id'];
1386
            $old_previous = $row_select['previous_item_id'];
1387
            $old_next = $row_select['next_item_id'];
1388
            $old_order = $row_select['display_order'];
1389
            $old_prerequisite = $row_select['prerequisite'];
1390
            $old_max_time_allowed = $row_select['max_time_allowed'];
1391
1392
            /* BEGIN -- virtually remove the current item id */
1393
            /* for the next and previous item it is like the current item doesn't exist anymore */
1394
            if ($old_previous != 0) {
1395
                // Next
1396
                $sql = "UPDATE $tbl_lp_item
1397
                        SET next_item_id = $old_next
1398
                        WHERE iid = $old_previous";
1399
                Database::query($sql);
1400
            }
1401
1402
            if (!empty($old_next)) {
1403
                // Previous
1404
                $sql = "UPDATE $tbl_lp_item
1405
                        SET previous_item_id = $old_previous
1406
                        WHERE iid = $old_next";
1407
                Database::query($sql);
1408
            }
1409
1410
            // display_order - 1 for every item with a display_order
1411
            // bigger then the display_order of the current item.
1412
            $sql = "UPDATE $tbl_lp_item
1413
                    SET display_order = display_order - 1
1414
                    WHERE
1415
                        c_id = $course_id AND
1416
                        display_order > $old_order AND
1417
                        lp_id = ".$this->lp_id." AND
1418
                        parent_item_id = $old_parent";
1419
            Database::query($sql);
1420
            /* END -- virtually remove the current item id */
1421
1422
            /* BEGIN -- update the current item id to his new location */
1423
            if ($previous == 0) {
1424
                // Select the data of the item that should come after the current item.
1425
                $sql = "SELECT id, display_order
1426
                        FROM $tbl_lp_item
1427
                        WHERE
1428
                            c_id = $course_id AND
1429
                            lp_id = ".$this->lp_id." AND
1430
                            parent_item_id = $parent AND
1431
                            previous_item_id = $previous";
1432
                $res_select_old = Database::query($sql);
1433
                $row_select_old = Database::fetch_array($res_select_old);
1434
1435
                // If the new parent didn't have children before.
1436
                if (Database::num_rows($res_select_old) == 0) {
1437
                    $new_next = 0;
1438
                    $new_order = 1;
1439
                } else {
1440
                    $new_next = $row_select_old['id'];
1441
                    $new_order = $row_select_old['display_order'];
1442
                }
1443
            } else {
1444
                // Select the data of the item that should come before the current item.
1445
                $sql = "SELECT next_item_id, display_order
1446
                        FROM $tbl_lp_item
1447
                        WHERE iid = $previous";
1448
                $res_select_old = Database::query($sql);
1449
                $row_select_old = Database::fetch_array($res_select_old);
1450
                $new_next = $row_select_old['next_item_id'];
1451
                $new_order = $row_select_old['display_order'] + 1;
1452
            }
1453
1454
            // TODO: htmlspecialchars to be checked for encoding related problems.
1455
            // Update the current item with the new data.
1456
            $sql = "UPDATE $tbl_lp_item
1457
                    SET
1458
                        title = '".Database::escape_string($title)."',
1459
                        description = '".Database::escape_string($description)."',
1460
                        parent_item_id = $parent,
1461
                        previous_item_id = $previous,
1462
                        next_item_id = $new_next,
1463
                        display_order = $new_order
1464
                        $audio_update_sql
1465
                    WHERE iid = $id";
1466
            Database::query($sql);
1467
1468
            if ($previous != 0) {
1469
                // Update the previous item's next_item_id.
1470
                $sql = "UPDATE $tbl_lp_item
1471
                        SET next_item_id = $id
1472
                        WHERE iid = $previous";
1473
                Database::query($sql);
1474
            }
1475
1476
            if (!empty($new_next)) {
1477
                // Update the next item's previous_item_id.
1478
                $sql = "UPDATE $tbl_lp_item
1479
                        SET previous_item_id = $id
1480
                        WHERE iid = $new_next";
1481
                Database::query($sql);
1482
            }
1483
1484
            if ($old_prerequisite != $prerequisites) {
1485
                $sql = "UPDATE $tbl_lp_item
1486
                        SET prerequisite = '$prerequisites'
1487
                        WHERE iid = $id";
1488
                Database::query($sql);
1489
            }
1490
1491
            if ($old_max_time_allowed != $max_time_allowed) {
1492
                // update max time allowed
1493
                $sql = "UPDATE $tbl_lp_item
1494
                        SET max_time_allowed = $max_time_allowed
1495
                        WHERE iid = $id";
1496
                Database::query($sql);
1497
            }
1498
1499
            // Update all the items with the same or a bigger display_order than the current item.
1500
            $sql = "UPDATE $tbl_lp_item
1501
                    SET display_order = display_order + 1
1502
                    WHERE
1503
                       c_id = $course_id AND
1504
                       lp_id = ".$this->get_id()." AND
1505
                       iid <> $id AND
1506
                       parent_item_id = $parent AND
1507
                       display_order >= $new_order";
1508
            Database::query($sql);
1509
        }
1510
1511
        if ($row_select['item_type'] == 'link') {
1512
            $link = new Link();
1513
            $linkId = $row_select['path'];
1514
            $link->updateLink($linkId, $url);
1515
        }
1516
    }
1517
1518
    /**
1519
     * Updates an item's prereq in place.
1520
     *
1521
     * @param int    $id              Element ID
1522
     * @param string $prerequisite_id Prerequisite Element ID
1523
     * @param int    $minScore        Prerequisite min score
1524
     * @param int    $maxScore        Prerequisite max score
1525
     *
1526
     * @return bool True on success, false on error
1527
     */
1528
    public function edit_item_prereq(
1529
        $id,
1530
        $prerequisite_id,
1531
        $minScore = 0,
1532
        $maxScore = 100
1533
    ) {
1534
        $id = (int) $id;
1535
        $prerequisite_id = (int) $prerequisite_id;
1536
1537
        if (empty($id)) {
1538
            return false;
1539
        }
1540
1541
        if (empty($minScore) || $minScore < 0) {
1542
            $minScore = 0;
1543
        }
1544
1545
        if (empty($maxScore) || $maxScore < 0) {
1546
            $maxScore = 100;
1547
        }
1548
1549
        $minScore = floatval($minScore);
1550
        $maxScore = floatval($maxScore);
1551
1552
        if (empty($prerequisite_id)) {
1553
            $prerequisite_id = 'NULL';
1554
            $minScore = 0;
1555
            $maxScore = 100;
1556
        }
1557
1558
        $table = Database::get_course_table(TABLE_LP_ITEM);
1559
        $sql = " UPDATE $table
1560
                 SET
1561
                    prerequisite = $prerequisite_id ,
1562
                    prerequisite_min_score = $minScore ,
1563
                    prerequisite_max_score = $maxScore
1564
                 WHERE iid = $id";
1565
        Database::query($sql);
1566
1567
        return true;
1568
    }
1569
1570
    /**
1571
     * Get the specific prefix index terms of this learning path.
1572
     *
1573
     * @param string $prefix
1574
     *
1575
     * @return array Array of terms
1576
     */
1577
    public function get_common_index_terms_by_prefix($prefix)
1578
    {
1579
        require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
1580
        $terms = get_specific_field_values_list_by_prefix(
1581
            $prefix,
1582
            $this->cc,
1583
            TOOL_LEARNPATH,
1584
            $this->lp_id
1585
        );
1586
        $prefix_terms = [];
1587
        if (!empty($terms)) {
1588
            foreach ($terms as $term) {
1589
                $prefix_terms[] = $term['value'];
1590
            }
1591
        }
1592
1593
        return $prefix_terms;
1594
    }
1595
1596
    /**
1597
     * Gets the number of items currently completed.
1598
     *
1599
     * @param bool $failedStatusException flag to determine the failed status is not considered progressed
1600
     *
1601
     * @return int The number of items currently completed
1602
     */
1603
    public function get_complete_items_count($failedStatusException = false)
1604
    {
1605
        $i = 0;
1606
        $completedStatusList = [
1607
            'completed',
1608
            'passed',
1609
            'succeeded',
1610
            'browsed',
1611
        ];
1612
1613
        if (!$failedStatusException) {
1614
            $completedStatusList[] = 'failed';
1615
        }
1616
1617
        foreach ($this->items as $id => $dummy) {
1618
            // Trying failed and browsed considered "progressed" as well.
1619
            if ($this->items[$id]->status_is($completedStatusList) &&
1620
                $this->items[$id]->get_type() != 'dir'
1621
            ) {
1622
                $i++;
1623
            }
1624
        }
1625
1626
        return $i;
1627
    }
1628
1629
    /**
1630
     * Gets the current item ID.
1631
     *
1632
     * @return int The current learnpath item id
1633
     */
1634
    public function get_current_item_id()
1635
    {
1636
        $current = 0;
1637
        if (!empty($this->current)) {
1638
            $current = (int) $this->current;
1639
        }
1640
1641
        return $current;
1642
    }
1643
1644
    /**
1645
     * Force to get the first learnpath item id.
1646
     *
1647
     * @return int The current learnpath item id
1648
     */
1649
    public function get_first_item_id()
1650
    {
1651
        $current = 0;
1652
        if (is_array($this->ordered_items)) {
1653
            $current = $this->ordered_items[0];
1654
        }
1655
1656
        return $current;
1657
    }
1658
1659
    /**
1660
     * Gets the total number of items available for viewing in this SCORM.
1661
     *
1662
     * @return int The total number of items
1663
     */
1664
    public function get_total_items_count()
1665
    {
1666
        return count($this->items);
1667
    }
1668
1669
    /**
1670
     * Gets the total number of items available for viewing in this SCORM but without chapters.
1671
     *
1672
     * @return int The total no-chapters number of items
1673
     */
1674
    public function getTotalItemsCountWithoutDirs()
1675
    {
1676
        $total = 0;
1677
        $typeListNotToCount = self::getChapterTypes();
1678
        foreach ($this->items as $temp2) {
1679
            if (!in_array($temp2->get_type(), $typeListNotToCount)) {
1680
                $total++;
1681
            }
1682
        }
1683
1684
        return $total;
1685
    }
1686
1687
    /**
1688
     *  Sets the first element URL.
1689
     */
1690
    public function first()
1691
    {
1692
        if ($this->debug > 0) {
1693
            error_log('In learnpath::first()', 0);
1694
            error_log('$this->last_item_seen '.$this->last_item_seen);
1695
        }
1696
1697
        // Test if the last_item_seen exists and is not a dir.
1698
        if (count($this->ordered_items) == 0) {
1699
            $this->index = 0;
1700
        }
1701
1702
        if (!empty($this->last_item_seen) &&
1703
            !empty($this->items[$this->last_item_seen]) &&
1704
            $this->items[$this->last_item_seen]->get_type() != 'dir'
1705
            //with this change (below) the LP will NOT go to the next item, it will take lp item we left
1706
            //&& !$this->items[$this->last_item_seen]->is_done()
1707
        ) {
1708
            if ($this->debug > 2) {
1709
                error_log(
1710
                    'In learnpath::first() - Last item seen is '.$this->last_item_seen.' of type '.
1711
                    $this->items[$this->last_item_seen]->get_type()
1712
                );
1713
            }
1714
            $index = -1;
1715
            foreach ($this->ordered_items as $myindex => $item_id) {
1716
                if ($item_id == $this->last_item_seen) {
1717
                    $index = $myindex;
1718
                    break;
1719
                }
1720
            }
1721
            if ($index == -1) {
1722
                // Index hasn't changed, so item not found - panic (this shouldn't happen).
1723
                if ($this->debug > 2) {
1724
                    error_log('Last item ('.$this->last_item_seen.') was found in items but not in ordered_items, panic!', 0);
1725
                }
1726
1727
                return false;
1728
            } else {
1729
                $this->last = $this->last_item_seen;
1730
                $this->current = $this->last_item_seen;
1731
                $this->index = $index;
1732
            }
1733
        } else {
1734
            if ($this->debug > 2) {
1735
                error_log('In learnpath::first() - No last item seen', 0);
1736
            }
1737
            $index = 0;
1738
            // Loop through all ordered items and stop at the first item that is
1739
            // not a directory *and* that has not been completed yet.
1740
            while (!empty($this->ordered_items[$index]) &&
1741
                is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') &&
1742
                (
1743
                    $this->items[$this->ordered_items[$index]]->get_type() == 'dir' ||
1744
                    $this->items[$this->ordered_items[$index]]->is_done() === true
1745
                ) && $index < $this->max_ordered_items) {
1746
                $index++;
1747
            }
1748
1749
            $this->last = $this->current;
1750
            // current is
1751
            $this->current = isset($this->ordered_items[$index]) ? $this->ordered_items[$index] : null;
1752
            $this->index = $index;
1753
            if ($this->debug > 2) {
1754
                error_log('$index '.$index);
1755
                error_log('In learnpath::first() - No last item seen');
1756
                error_log('New last = '.$this->last.'('.$this->ordered_items[$index].')');
1757
            }
1758
        }
1759
        if ($this->debug > 2) {
1760
            error_log('In learnpath::first() - First item is '.$this->get_current_item_id());
1761
        }
1762
    }
1763
1764
    /**
1765
     * Gets the js library from the database.
1766
     *
1767
     * @return string The name of the javascript library to be used
1768
     */
1769
    public function get_js_lib()
1770
    {
1771
        $lib = '';
1772
        if (!empty($this->js_lib)) {
1773
            $lib = $this->js_lib;
1774
        }
1775
1776
        return $lib;
1777
    }
1778
1779
    /**
1780
     * Gets the learnpath database ID.
1781
     *
1782
     * @return int Learnpath ID in the lp table
1783
     */
1784
    public function get_id()
1785
    {
1786
        if (!empty($this->lp_id)) {
1787
            return (int) $this->lp_id;
1788
        }
1789
1790
        return 0;
1791
    }
1792
1793
    /**
1794
     * Gets the last element URL.
1795
     *
1796
     * @return string URL to load into the viewer
1797
     */
1798
    public function get_last()
1799
    {
1800
        // This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
1801
        if (count($this->ordered_items) > 0) {
1802
            $this->index = count($this->ordered_items) - 1;
1803
1804
            return $this->ordered_items[$this->index];
1805
        }
1806
1807
        return false;
1808
    }
1809
1810
    /**
1811
     * Get the last element in the first level.
1812
     * Unlike learnpath::get_last this function doesn't consider the subsection' elements.
1813
     *
1814
     * @return mixed
1815
     */
1816
    public function getLastInFirstLevel()
1817
    {
1818
        try {
1819
            $lastId = Database::getManager()
1820
                ->createQuery('SELECT i.iid FROM ChamiloCourseBundle:CLpItem i
1821
                WHERE i.lpId = :lp AND i.parentItemId = 0 AND i.itemType != :type ORDER BY i.displayOrder DESC')
1822
                ->setMaxResults(1)
1823
                ->setParameters(['lp' => $this->lp_id, 'type' => TOOL_LP_FINAL_ITEM])
1824
                ->getSingleScalarResult();
1825
1826
            return $lastId;
1827
        } catch (Exception $exception) {
1828
            return 0;
1829
        }
1830
    }
1831
1832
    /**
1833
     * Gets the navigation bar for the learnpath display screen.
1834
     *
1835
     * @param string $barId
1836
     *
1837
     * @return string The HTML string to use as a navigation bar
1838
     */
1839
    public function get_navigation_bar($barId = '')
1840
    {
1841
        if (empty($barId)) {
1842
            $barId = 'control-top';
1843
        }
1844
        $lpId = $this->lp_id;
1845
        $mycurrentitemid = $this->get_current_item_id();
1846
1847
        $reportingText = get_lang('Reporting');
1848
        $previousText = get_lang('ScormPrevious');
1849
        $nextText = get_lang('ScormNext');
1850
        $fullScreenText = get_lang('ScormExitFullScreen');
1851
1852
        $settings = api_get_configuration_value('lp_view_settings');
1853
        $display = isset($settings['display']) ? $settings['display'] : false;
1854
        $reportingIcon = '
1855
            <a class="icon-toolbar"
1856
                id="stats_link"
1857
                href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lpId.'"
1858
                onclick="window.parent.API.save_asset(); return true;"
1859
                target="content_name" title="'.$reportingText.'">
1860
                <span class="fa fa-info"></span><span class="sr-only">'.$reportingText.'</span>
1861
            </a>';
1862
1863
        if (!empty($display)) {
1864
            $showReporting = isset($display['show_reporting_icon']) ? $display['show_reporting_icon'] : true;
1865
            if ($showReporting === false) {
1866
                $reportingIcon = '';
1867
            }
1868
        }
1869
1870
        $hideArrows = false;
1871
        if (isset($settings['display']) && isset($settings['display']['hide_lp_arrow_navigation'])) {
1872
            $hideArrows = $settings['display']['hide_lp_arrow_navigation'];
1873
        }
1874
1875
        $previousIcon = '';
1876
        $nextIcon = '';
1877
        if ($hideArrows === false) {
1878
            $previousIcon = '
1879
                <a class="icon-toolbar" id="scorm-previous" href="#"
1880
                    onclick="switch_item('.$mycurrentitemid.',\'previous\');return false;" title="'.$previousText.'">
1881
                    <span class="fa fa-chevron-left"></span><span class="sr-only">'.$previousText.'</span>
1882
                </a>';
1883
1884
            $nextIcon = '
1885
                <a class="icon-toolbar" id="scorm-next" href="#"
1886
                    onclick="switch_item('.$mycurrentitemid.',\'next\');return false;" title="'.$nextText.'">
1887
                    <span class="fa fa-chevron-right"></span><span class="sr-only">'.$nextText.'</span>
1888
                </a>';
1889
        }
1890
1891
        if ($this->mode === 'fullscreen') {
1892
            $navbar = '
1893
                  <span id="'.$barId.'" class="buttons">
1894
                    '.$reportingIcon.'
1895
                    '.$previousIcon.'
1896
                    '.$nextIcon.'
1897
                    <a class="icon-toolbar" id="view-embedded"
1898
                        href="lp_controller.php?action=mode&mode=embedded" target="_top" title="'.$fullScreenText.'">
1899
                        <span class="fa fa-columns"></span><span class="sr-only">'.$fullScreenText.'</span>
1900
                    </a>
1901
                  </span>';
1902
        } else {
1903
            $navbar = '
1904
                 <span id="'.$barId.'" class="buttons text-right">
1905
                    '.$reportingIcon.'
1906
                    '.$previousIcon.'
1907
                    '.$nextIcon.'
1908
                </span>';
1909
        }
1910
1911
        return $navbar;
1912
    }
1913
1914
    /**
1915
     * Gets the next resource in queue (url).
1916
     *
1917
     * @return string URL to load into the viewer
1918
     */
1919
    public function get_next_index()
1920
    {
1921
        // TODO
1922
        $index = $this->index;
1923
        $index++;
1924
        while (
1925
            !empty($this->ordered_items[$index]) && ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') &&
1926
            $index < $this->max_ordered_items
1927
        ) {
1928
            $index++;
1929
            if ($index == $this->max_ordered_items) {
1930
                if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir') {
1931
                    return $this->index;
1932
                }
1933
1934
                return $index;
1935
            }
1936
        }
1937
        if (empty($this->ordered_items[$index])) {
1938
            return $this->index;
1939
        }
1940
1941
        return $index;
1942
    }
1943
1944
    /**
1945
     * Gets item_id for the next element.
1946
     *
1947
     * @return int Next item (DB) ID
1948
     */
1949
    public function get_next_item_id()
1950
    {
1951
        $new_index = $this->get_next_index();
1952
        if (!empty($new_index)) {
1953
            if (isset($this->ordered_items[$new_index])) {
1954
                return $this->ordered_items[$new_index];
1955
            }
1956
        }
1957
1958
        return 0;
1959
    }
1960
1961
    /**
1962
     * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...).
1963
     *
1964
     * Generally, the package provided is in the form of a zip file, so the function
1965
     * has been written to test a zip file. If not a zip, the function will return the
1966
     * default return value: ''
1967
     *
1968
     * @param string $file_path the path to the file
1969
     * @param string $file_name the original name of the file
1970
     *
1971
     * @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
1972
     */
1973
    public static function getPackageType($file_path, $file_name)
1974
    {
1975
        // Get name of the zip file without the extension.
1976
        $file_info = pathinfo($file_name);
1977
        $extension = $file_info['extension']; // Extension only.
1978
        if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), [
1979
                'dll',
1980
                'exe',
1981
            ])) {
1982
            return 'oogie';
1983
        }
1984
        if (!empty($_POST['woogie']) && !in_array(strtolower($extension), [
1985
                'dll',
1986
                'exe',
1987
            ])) {
1988
            return 'woogie';
1989
        }
1990
1991
        $zipFile = new PclZip($file_path);
1992
        // Check the zip content (real size and file extension).
1993
        $zipContentArray = $zipFile->listContent();
1994
        $package_type = '';
1995
        $manifest = '';
1996
        $aicc_match_crs = 0;
1997
        $aicc_match_au = 0;
1998
        $aicc_match_des = 0;
1999
        $aicc_match_cst = 0;
2000
        $countItems = 0;
2001
2002
        // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
2003
        if (is_array($zipContentArray)) {
2004
            $countItems = count($zipContentArray);
2005
            if ($countItems > 0) {
2006
                foreach ($zipContentArray as $thisContent) {
2007
                    if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
2008
                        // New behaviour: Don't do anything. These files will be removed in scorm::import_package.
2009
                    } elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
2010
                        $manifest = $thisContent['filename']; // Just the relative directory inside scorm/
2011
                        $package_type = 'scorm';
2012
                        break; // Exit the foreach loop.
2013
                    } elseif (
2014
                        preg_match('/aicc\//i', $thisContent['filename']) ||
2015
                        in_array(
2016
                            strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
2017
                            ['crs', 'au', 'des', 'cst']
2018
                        )
2019
                    ) {
2020
                        $ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
2021
                        switch ($ext) {
2022
                            case 'crs':
2023
                                $aicc_match_crs = 1;
2024
                                break;
2025
                            case 'au':
2026
                                $aicc_match_au = 1;
2027
                                break;
2028
                            case 'des':
2029
                                $aicc_match_des = 1;
2030
                                break;
2031
                            case 'cst':
2032
                                $aicc_match_cst = 1;
2033
                                break;
2034
                            default:
2035
                                break;
2036
                        }
2037
                        //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
2038
                    } else {
2039
                        $package_type = '';
2040
                    }
2041
                }
2042
            }
2043
        }
2044
2045
        if (empty($package_type) && 4 == ($aicc_match_crs + $aicc_match_au + $aicc_match_des + $aicc_match_cst)) {
2046
            // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)).
2047
            $package_type = 'aicc';
2048
        }
2049
2050
        // Try with chamilo course builder
2051
        if (empty($package_type)) {
2052
            // Sometimes users will try to upload an empty zip, or a zip with
2053
            // only a folder. Catch that and make the calling function aware.
2054
            // If the single file was the imsmanifest.xml, then $package_type
2055
            // would be 'scorm' and we wouldn't be here.
2056
            if ($countItems < 2) {
2057
                return 'error-empty-package';
2058
            }
2059
            $package_type = 'chamilo';
2060
        }
2061
2062
        return $package_type;
2063
    }
2064
2065
    /**
2066
     * Gets the previous resource in queue (url). Also initialises time values for this viewing.
2067
     *
2068
     * @return string URL to load into the viewer
2069
     */
2070
    public function get_previous_index()
2071
    {
2072
        $index = $this->index;
2073
        if (isset($this->ordered_items[$index - 1])) {
2074
            $index--;
2075
            while (isset($this->ordered_items[$index]) &&
2076
                ($this->items[$this->ordered_items[$index]]->get_type() == 'dir')
2077
            ) {
2078
                $index--;
2079
                if ($index < 0) {
2080
                    return $this->index;
2081
                }
2082
            }
2083
        }
2084
2085
        return $index;
2086
    }
2087
2088
    /**
2089
     * Gets item_id for the next element.
2090
     *
2091
     * @return int Previous item (DB) ID
2092
     */
2093
    public function get_previous_item_id()
2094
    {
2095
        $index = $this->get_previous_index();
2096
2097
        return $this->ordered_items[$index];
2098
    }
2099
2100
    /**
2101
     * Returns the HTML necessary to print a mediaplayer block inside a page.
2102
     *
2103
     * @param int    $lpItemId
2104
     * @param string $autostart
2105
     *
2106
     * @return string The mediaplayer HTML
2107
     */
2108
    public function get_mediaplayer($lpItemId, $autostart = 'true')
2109
    {
2110
        $course_id = api_get_course_int_id();
2111
        $courseInfo = api_get_course_info();
2112
        $lpItemId = (int) $lpItemId;
2113
2114
        if (empty($courseInfo) || empty($lpItemId)) {
2115
            return '';
2116
        }
2117
        $item = isset($this->items[$lpItemId]) ? $this->items[$lpItemId] : null;
2118
2119
        if (empty($item)) {
2120
            return '';
2121
        }
2122
2123
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
2124
        $tbl_lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW);
2125
        $itemViewId = (int) $item->db_item_view_id;
2126
2127
        // Getting all the information about the item.
2128
        $sql = "SELECT lp_view.status
2129
                FROM $tbl_lp_item as lpi
2130
                INNER JOIN $tbl_lp_item_view as lp_view
2131
                ON (lpi.iid = lp_view.lp_item_id)
2132
                WHERE
2133
                    lp_view.iid = $itemViewId AND
2134
                    lpi.iid = $lpItemId AND
2135
                    lp_view.c_id = $course_id";
2136
        $result = Database::query($sql);
2137
        $row = Database::fetch_assoc($result);
2138
        $output = '';
2139
        $audio = $item->audio;
2140
2141
        if (!empty($audio)) {
2142
            $list = $_SESSION['oLP']->get_toc();
2143
2144
            switch ($item->get_type()) {
2145
                case 'quiz':
2146
                    $type_quiz = false;
2147
                    foreach ($list as $toc) {
2148
                        if ($toc['id'] == $_SESSION['oLP']->current) {
2149
                            $type_quiz = true;
2150
                        }
2151
                    }
2152
2153
                    if ($type_quiz) {
2154
                        if ($_SESSION['oLP']->prevent_reinit == 1) {
2155
                            $autostart_audio = $row['status'] === 'completed' ? 'false' : 'true';
2156
                        } else {
2157
                            $autostart_audio = $autostart;
2158
                        }
2159
                    }
2160
                    break;
2161
                case TOOL_READOUT_TEXT:
2162
                    $autostart_audio = 'false';
2163
                    break;
2164
                default:
2165
                    $autostart_audio = 'true';
2166
            }
2167
2168
            $file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document'.$audio;
2169
            $url = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document'.$audio.'?'.api_get_cidreq();
2170
2171
            $player = Display::getMediaPlayer(
2172
                $file,
2173
                [
2174
                    'id' => 'lp_audio_media_player',
2175
                    'url' => $url,
2176
                    'autoplay' => $autostart_audio,
2177
                    'width' => '100%',
2178
                ]
2179
            );
2180
2181
            // The mp3 player.
2182
            $output = '<div id="container">';
2183
            $output .= $player;
2184
            $output .= '</div>';
2185
        }
2186
2187
        return $output;
2188
    }
2189
2190
    /**
2191
     * @param int   $studentId
2192
     * @param int   $prerequisite
2193
     * @param array $courseInfo
2194
     * @param int   $sessionId
2195
     *
2196
     * @return bool
2197
     */
2198
    public static function isBlockedByPrerequisite(
2199
        $studentId,
2200
        $prerequisite,
2201
        $courseInfo,
2202
        $sessionId
2203
    ) {
2204
        if (empty($courseInfo)) {
2205
            return false;
2206
        }
2207
2208
        $courseId = $courseInfo['real_id'];
2209
2210
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
2211
        if ($allow) {
2212
            if (api_is_allowed_to_edit() ||
2213
                api_is_platform_admin(true) ||
2214
                api_is_drh() ||
2215
                api_is_coach($sessionId, $courseId, false)
2216
            ) {
2217
                return false;
2218
            }
2219
        }
2220
2221
        $isBlocked = false;
2222
        if (!empty($prerequisite)) {
2223
            $progress = self::getProgress(
2224
                $prerequisite,
2225
                $studentId,
2226
                $courseId,
2227
                $sessionId
2228
            );
2229
            if ($progress < 100) {
2230
                $isBlocked = true;
2231
            }
2232
2233
            if (Tracking::minimumTimeAvailable($sessionId, $courseId)) {
2234
                // Block if it does not exceed minimum time
2235
                // Minimum time (in minutes) to pass the learning path
2236
                $accumulateWorkTime = self::getAccumulateWorkTimePrerequisite($prerequisite, $courseId);
2237
2238
                if ($accumulateWorkTime > 0) {
2239
                    // Total time in course (sum of times in learning paths from course)
2240
                    $accumulateWorkTimeTotal = self::getAccumulateWorkTimeTotal($courseId);
2241
2242
                    // Connect with the plugin_licences_course_session table
2243
                    // which indicates what percentage of the time applies
2244
                    // Minimum connection percentage
2245
                    $perc = 100;
2246
                    // Time from the course
2247
                    $tc = $accumulateWorkTimeTotal;
2248
2249
                    // Percentage of the learning paths
2250
                    $pl = $accumulateWorkTime / $accumulateWorkTimeTotal;
2251
                    // Minimum time for each learning path
2252
                    $accumulateWorkTime = ($pl * $tc * $perc / 100);
2253
2254
                    // Spent time (in seconds) so far in the learning path
2255
                    $lpTimeList = Tracking::getCalculateTime($studentId, $courseId, $sessionId);
2256
                    $lpTime = isset($lpTimeList[TOOL_LEARNPATH][$prerequisite]) ? $lpTimeList[TOOL_LEARNPATH][$prerequisite] : 0;
2257
2258
                    if ($lpTime < ($accumulateWorkTime * 60)) {
2259
                        $isBlocked = true;
2260
                    }
2261
                }
2262
            }
2263
        }
2264
2265
        return $isBlocked;
2266
    }
2267
2268
    /**
2269
     * Checks if the learning path is visible for student after the progress
2270
     * of its prerequisite is completed, considering the time availability and
2271
     * the LP visibility.
2272
     *
2273
     * @param int   $lp_id
2274
     * @param int   $student_id
2275
     * @param array $courseInfo
2276
     * @param int   $sessionId
2277
     *
2278
     * @return bool
2279
     */
2280
    public static function is_lp_visible_for_student(
2281
        $lp_id,
2282
        $student_id,
2283
        $courseInfo = [],
2284
        $sessionId = 0
2285
    ) {
2286
        $courseInfo = empty($courseInfo) ? api_get_course_info() : $courseInfo;
2287
        $lp_id = (int) $lp_id;
2288
        $sessionId = (int) $sessionId;
2289
2290
        if (empty($courseInfo)) {
2291
            return false;
2292
        }
2293
2294
        if (empty($sessionId)) {
2295
            $sessionId = api_get_session_id();
2296
        }
2297
2298
        $courseId = $courseInfo['real_id'];
2299
2300
        $itemInfo = api_get_item_property_info(
2301
            $courseId,
2302
            TOOL_LEARNPATH,
2303
            $lp_id,
2304
            $sessionId
2305
        );
2306
2307
        // If the item was deleted.
2308
        if (isset($itemInfo['visibility']) && $itemInfo['visibility'] == 2) {
2309
            return false;
2310
        }
2311
2312
        // @todo remove this query and load the row info as a parameter
2313
        $table = Database::get_course_table(TABLE_LP_MAIN);
2314
        // Get current prerequisite
2315
        $sql = "SELECT id, prerequisite, subscribe_users, publicated_on, expired_on, category_id
2316
                FROM $table
2317
                WHERE iid = $lp_id";
2318
        $rs = Database::query($sql);
2319
        $now = time();
2320
        if (Database::num_rows($rs) > 0) {
2321
            $row = Database::fetch_array($rs, 'ASSOC');
2322
2323
            if (!empty($row['category_id'])) {
2324
                $category = self::getCategory($row['category_id']);
2325
                if (self::categoryIsVisibleForStudent($category, api_get_user_entity($student_id)) === false) {
2326
                    return false;
2327
                }
2328
            }
2329
2330
            $prerequisite = $row['prerequisite'];
2331
            $is_visible = true;
2332
2333
            $isBlocked = self::isBlockedByPrerequisite(
2334
                $student_id,
2335
                $prerequisite,
2336
                $courseInfo,
2337
                $sessionId
2338
            );
2339
2340
            if ($isBlocked) {
2341
                $is_visible = false;
2342
            }
2343
2344
            // Also check the time availability of the LP
2345
            if ($is_visible) {
2346
                // Adding visibility restrictions
2347
                if (!empty($row['publicated_on'])) {
2348
                    if ($now < api_strtotime($row['publicated_on'], 'UTC')) {
2349
                        $is_visible = false;
2350
                    }
2351
                }
2352
                // Blocking empty start times see BT#2800
2353
                global $_custom;
2354
                if (isset($_custom['lps_hidden_when_no_start_date']) &&
2355
                    $_custom['lps_hidden_when_no_start_date']
2356
                ) {
2357
                    if (empty($row['publicated_on'])) {
2358
                        $is_visible = false;
2359
                    }
2360
                }
2361
2362
                if (!empty($row['expired_on'])) {
2363
                    if ($now > api_strtotime($row['expired_on'], 'UTC')) {
2364
                        $is_visible = false;
2365
                    }
2366
                }
2367
            }
2368
2369
            if ($is_visible) {
2370
                $subscriptionSettings = self::getSubscriptionSettings();
2371
2372
                // Check if the subscription users/group to a LP is ON
2373
                if (isset($row['subscribe_users']) && $row['subscribe_users'] == 1 &&
2374
                    $subscriptionSettings['allow_add_users_to_lp'] === true
2375
                ) {
2376
                    // Try group
2377
                    $is_visible = false;
2378
                    // Checking only the user visibility
2379
                    $userVisibility = api_get_item_visibility(
2380
                        $courseInfo,
2381
                        'learnpath',
2382
                        $row['id'],
2383
                        $sessionId,
2384
                        $student_id,
2385
                        'LearnpathSubscription'
2386
                    );
2387
2388
                    if ($userVisibility == 1) {
2389
                        $is_visible = true;
2390
                    } else {
2391
                        $userGroups = GroupManager::getAllGroupPerUserSubscription($student_id, $courseId);
2392
                        if (!empty($userGroups)) {
2393
                            foreach ($userGroups as $groupInfo) {
2394
                                $groupId = $groupInfo['iid'];
2395
                                $userVisibility = api_get_item_visibility(
2396
                                    $courseInfo,
2397
                                    'learnpath',
2398
                                    $row['id'],
2399
                                    $sessionId,
2400
                                    null,
2401
                                    'LearnpathSubscription',
2402
                                    $groupId
2403
                                );
2404
2405
                                if ($userVisibility == 1) {
2406
                                    $is_visible = true;
2407
                                    break;
2408
                                }
2409
                            }
2410
                        }
2411
                    }
2412
                }
2413
            }
2414
2415
            return $is_visible;
2416
        }
2417
2418
        return false;
2419
    }
2420
2421
    /**
2422
     * @param int $lpId
2423
     * @param int $userId
2424
     * @param int $courseId
2425
     * @param int $sessionId
2426
     *
2427
     * @return int
2428
     */
2429
    public static function getProgress($lpId, $userId, $courseId, $sessionId = 0)
2430
    {
2431
        $lpId = (int) $lpId;
2432
        $userId = (int) $userId;
2433
        $courseId = (int) $courseId;
2434
        $sessionId = (int) $sessionId;
2435
2436
        $sessionCondition = api_get_session_condition($sessionId);
2437
        $table = Database::get_course_table(TABLE_LP_VIEW);
2438
        $sql = "SELECT progress FROM $table
2439
                WHERE
2440
                    c_id = $courseId AND
2441
                    lp_id = $lpId AND
2442
                    user_id = $userId $sessionCondition ";
2443
        $res = Database::query($sql);
2444
2445
        $progress = 0;
2446
        if (Database::num_rows($res) > 0) {
2447
            $row = Database::fetch_array($res);
2448
            $progress = (int) $row['progress'];
2449
        }
2450
2451
        return $progress;
2452
    }
2453
2454
    /**
2455
     * @param array $lpList
2456
     * @param int   $userId
2457
     * @param int   $courseId
2458
     * @param int   $sessionId
2459
     *
2460
     * @return array
2461
     */
2462
    public static function getProgressFromLpList($lpList, $userId, $courseId, $sessionId = 0)
2463
    {
2464
        $lpList = array_map('intval', $lpList);
2465
        if (empty($lpList)) {
2466
            return [];
2467
        }
2468
2469
        $lpList = implode("','", $lpList);
2470
2471
        $userId = (int) $userId;
2472
        $courseId = (int) $courseId;
2473
        $sessionId = (int) $sessionId;
2474
2475
        $sessionCondition = api_get_session_condition($sessionId);
2476
        $table = Database::get_course_table(TABLE_LP_VIEW);
2477
        $sql = "SELECT lp_id, progress FROM $table
2478
                WHERE
2479
                    c_id = $courseId AND
2480
                    lp_id IN ('".$lpList."') AND
2481
                    user_id = $userId $sessionCondition ";
2482
        $res = Database::query($sql);
2483
2484
        if (Database::num_rows($res) > 0) {
2485
            $list = [];
2486
            while ($row = Database::fetch_array($res)) {
2487
                $list[$row['lp_id']] = $row['progress'];
2488
            }
2489
2490
            return $list;
2491
        }
2492
2493
        return [];
2494
    }
2495
2496
    /**
2497
     * Displays a progress bar
2498
     * completed so far.
2499
     *
2500
     * @param int    $percentage Progress value to display
2501
     * @param string $text_add   Text to display near the progress value
2502
     *
2503
     * @return string HTML string containing the progress bar
2504
     */
2505
    public static function get_progress_bar($percentage = -1, $text_add = '')
2506
    {
2507
        $text = $percentage.$text_add;
2508
        $output = '<div class="progress">
2509
            <div id="progress_bar_value"
2510
                class="progress-bar progress-bar-success" role="progressbar"
2511
                aria-valuenow="'.$percentage.'" aria-valuemin="0" aria-valuemax="100" style="width: '.$text.';">
2512
            '.$text.'
2513
            </div>
2514
        </div>';
2515
2516
        return $output;
2517
    }
2518
2519
    /**
2520
     * @param string $mode can be '%' or 'abs'
2521
     *                     otherwise this value will be used $this->progress_bar_mode
2522
     *
2523
     * @return string
2524
     */
2525
    public function getProgressBar($mode = null)
2526
    {
2527
        list($percentage, $text_add) = $this->get_progress_bar_text($mode);
2528
2529
        return self::get_progress_bar($percentage, $text_add);
2530
    }
2531
2532
    /**
2533
     * Gets the progress bar info to display inside the progress bar.
2534
     * Also used by scorm_api.php.
2535
     *
2536
     * @param string $mode Mode of display (can be '%' or 'abs').abs means
2537
     *                     we display a number of completed elements per total elements
2538
     * @param int    $add  Additional steps to fake as completed
2539
     *
2540
     * @return array Percentage or number and symbol (% or /xx)
2541
     */
2542
    public function get_progress_bar_text($mode = '', $add = 0)
2543
    {
2544
        if (empty($mode)) {
2545
            $mode = $this->progress_bar_mode;
2546
        }
2547
        $text = '';
2548
        $percentage = 0;
2549
        // If the option to use the score as progress is set for this learning
2550
        // path, then the rules are completely different: we assume only one
2551
        // item exists and the progress of the LP depends on the score
2552
        $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
2553
        if ($scoreAsProgressSetting === true) {
2554
            $scoreAsProgress = $this->getUseScoreAsProgress();
2555
            if ($scoreAsProgress) {
2556
                // Get single item's score
2557
                $itemId = $this->get_current_item_id();
2558
                $item = $this->getItem($itemId);
2559
                $score = $item->get_score();
2560
                $maxScore = $item->get_max();
2561
                if ($mode = '%') {
2562
                    if (!empty($maxScore)) {
2563
                        $percentage = ((float) $score / (float) $maxScore) * 100;
2564
                    }
2565
                    $percentage = number_format($percentage, 0);
2566
                    $text = '%';
2567
                } else {
2568
                    $percentage = $score;
2569
                    $text = '/'.$maxScore;
2570
                }
2571
2572
                return [$percentage, $text];
2573
            }
2574
        }
2575
        // otherwise just continue the normal processing of progress
2576
        $total_items = $this->getTotalItemsCountWithoutDirs();
2577
        $completeItems = $this->get_complete_items_count();
2578
        if ($add != 0) {
2579
            $completeItems += $add;
2580
        }
2581
        if ($completeItems > $total_items) {
2582
            $completeItems = $total_items;
2583
        }
2584
        if ($mode == '%') {
2585
            if ($total_items > 0) {
2586
                $percentage = ((float) $completeItems / (float) $total_items) * 100;
2587
            }
2588
            $percentage = number_format($percentage, 0);
2589
            $text = '%';
2590
        } elseif ($mode === 'abs') {
2591
            $percentage = $completeItems;
2592
            $text = '/'.$total_items;
2593
        }
2594
2595
        return [
2596
            $percentage,
2597
            $text,
2598
        ];
2599
    }
2600
2601
    /**
2602
     * Gets the progress bar mode.
2603
     *
2604
     * @return string The progress bar mode attribute
2605
     */
2606
    public function get_progress_bar_mode()
2607
    {
2608
        if (!empty($this->progress_bar_mode)) {
2609
            return $this->progress_bar_mode;
2610
        }
2611
2612
        return '%';
2613
    }
2614
2615
    /**
2616
     * Gets the learnpath theme (remote or local).
2617
     *
2618
     * @return string Learnpath theme
2619
     */
2620
    public function get_theme()
2621
    {
2622
        if (!empty($this->theme)) {
2623
            return $this->theme;
2624
        }
2625
2626
        return '';
2627
    }
2628
2629
    /**
2630
     * Gets the learnpath session id.
2631
     *
2632
     * @return int
2633
     */
2634
    public function get_lp_session_id()
2635
    {
2636
        if (!empty($this->lp_session_id)) {
2637
            return (int) $this->lp_session_id;
2638
        }
2639
2640
        return 0;
2641
    }
2642
2643
    /**
2644
     * Gets the learnpath image.
2645
     *
2646
     * @return string Web URL of the LP image
2647
     */
2648
    public function get_preview_image()
2649
    {
2650
        if (!empty($this->preview_image)) {
2651
            return $this->preview_image;
2652
        }
2653
2654
        return '';
2655
    }
2656
2657
    /**
2658
     * @param string $size
2659
     * @param string $path_type
2660
     *
2661
     * @return bool|string
2662
     */
2663
    public function get_preview_image_path($size = null, $path_type = 'web')
2664
    {
2665
        $preview_image = $this->get_preview_image();
2666
        if (isset($preview_image) && !empty($preview_image)) {
2667
            $image_sys_path = api_get_path(SYS_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2668
            $image_path = api_get_path(WEB_COURSE_PATH).$this->course_info['path'].'/upload/learning_path/images/';
2669
2670
            if (isset($size)) {
2671
                $info = pathinfo($preview_image);
2672
                $image_custom_size = $info['filename'].'.'.$size.'.'.$info['extension'];
2673
2674
                if (file_exists($image_sys_path.$image_custom_size)) {
2675
                    if ($path_type == 'web') {
2676
                        return $image_path.$image_custom_size;
2677
                    } else {
2678
                        return $image_sys_path.$image_custom_size;
2679
                    }
2680
                }
2681
            } else {
2682
                if ($path_type == 'web') {
2683
                    return $image_path.$preview_image;
2684
                } else {
2685
                    return $image_sys_path.$preview_image;
2686
                }
2687
            }
2688
        }
2689
2690
        return false;
2691
    }
2692
2693
    /**
2694
     * Gets the learnpath author.
2695
     *
2696
     * @return string LP's author
2697
     */
2698
    public function get_author()
2699
    {
2700
        if (!empty($this->author)) {
2701
            return $this->author;
2702
        }
2703
2704
        return '';
2705
    }
2706
2707
    /**
2708
     * Gets hide table of contents.
2709
     *
2710
     * @return int
2711
     */
2712
    public function getHideTableOfContents()
2713
    {
2714
        return (int) $this->hide_toc_frame;
2715
    }
2716
2717
    /**
2718
     * Generate a new prerequisites string for a given item. If this item was a sco and
2719
     * its prerequisites were strings (instead of IDs), then transform those strings into
2720
     * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table.
2721
     * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the
2722
     * same rule as the scormExport() method.
2723
     *
2724
     * @param int $item_id Item ID
2725
     *
2726
     * @return string Prerequisites string ready for the export as SCORM
2727
     */
2728
    public function get_scorm_prereq_string($item_id)
2729
    {
2730
        if ($this->debug > 0) {
2731
            error_log('In learnpath::get_scorm_prereq_string()');
2732
        }
2733
        if (!is_object($this->items[$item_id])) {
2734
            return false;
2735
        }
2736
        /** @var learnpathItem $oItem */
2737
        $oItem = $this->items[$item_id];
2738
        $prereq = $oItem->get_prereq_string();
2739
2740
        if (empty($prereq)) {
2741
            return '';
2742
        }
2743
        if (preg_match('/^\d+$/', $prereq) &&
2744
            isset($this->items[$prereq]) &&
2745
            is_object($this->items[$prereq])
2746
        ) {
2747
            // If the prerequisite is a simple integer ID and this ID exists as an item ID,
2748
            // then simply return it (with the ITEM_ prefix).
2749
            //return 'ITEM_' . $prereq;
2750
            return $this->items[$prereq]->ref;
2751
        } else {
2752
            if (isset($this->refs_list[$prereq])) {
2753
                // It's a simple string item from which the ID can be found in the refs list,
2754
                // so we can transform it directly to an ID for export.
2755
                return $this->items[$this->refs_list[$prereq]]->ref;
2756
            } elseif (isset($this->refs_list['ITEM_'.$prereq])) {
2757
                return $this->items[$this->refs_list['ITEM_'.$prereq]]->ref;
2758
            } else {
2759
                // The last case, if it's a complex form, then find all the IDs (SCORM strings)
2760
                // and replace them, one by one, by the internal IDs (chamilo db)
2761
                // TODO: Modify the '*' replacement to replace the multiplier in front of it
2762
                // by a space as well.
2763
                $find = [
2764
                    '&',
2765
                    '|',
2766
                    '~',
2767
                    '=',
2768
                    '<>',
2769
                    '{',
2770
                    '}',
2771
                    '*',
2772
                    '(',
2773
                    ')',
2774
                ];
2775
                $replace = [
2776
                    ' ',
2777
                    ' ',
2778
                    ' ',
2779
                    ' ',
2780
                    ' ',
2781
                    ' ',
2782
                    ' ',
2783
                    ' ',
2784
                    ' ',
2785
                    ' ',
2786
                ];
2787
                $prereq_mod = str_replace($find, $replace, $prereq);
2788
                $ids = explode(' ', $prereq_mod);
2789
                foreach ($ids as $id) {
2790
                    $id = trim($id);
2791
                    if (isset($this->refs_list[$id])) {
2792
                        $prereq = preg_replace(
2793
                            '/[^a-zA-Z_0-9]('.$id.')[^a-zA-Z_0-9]/',
2794
                            'ITEM_'.$this->refs_list[$id],
2795
                            $prereq
2796
                        );
2797
                    }
2798
                }
2799
2800
                return $prereq;
2801
            }
2802
        }
2803
    }
2804
2805
    /**
2806
     * Returns the XML DOM document's node.
2807
     *
2808
     * @param resource $children Reference to a list of objects to search for the given ITEM_*
2809
     * @param string   $id       The identifier to look for
2810
     *
2811
     * @return mixed The reference to the element found with that identifier. False if not found
2812
     */
2813
    public function get_scorm_xml_node(&$children, $id)
2814
    {
2815
        for ($i = 0; $i < $children->length; $i++) {
2816
            $item_temp = $children->item($i);
2817
            if ($item_temp->nodeName == 'item') {
2818
                if ($item_temp->getAttribute('identifier') == $id) {
2819
                    return $item_temp;
2820
                }
2821
            }
2822
            $subchildren = $item_temp->childNodes;
2823
            if ($subchildren && $subchildren->length > 0) {
2824
                $val = $this->get_scorm_xml_node($subchildren, $id);
2825
                if (is_object($val)) {
2826
                    return $val;
2827
                }
2828
            }
2829
        }
2830
2831
        return false;
2832
    }
2833
2834
    /**
2835
     * Gets the status list for all LP's items.
2836
     *
2837
     * @return array Array of [index] => [item ID => current status]
2838
     */
2839
    public function get_items_status_list()
2840
    {
2841
        $list = [];
2842
        foreach ($this->ordered_items as $item_id) {
2843
            $list[] = [
2844
                $item_id => $this->items[$item_id]->get_status(),
2845
            ];
2846
        }
2847
2848
        return $list;
2849
    }
2850
2851
    /**
2852
     * Return the number of interactions for the given learnpath Item View ID.
2853
     * This method can be used as static.
2854
     *
2855
     * @param int $lp_iv_id  Item View ID
2856
     * @param int $course_id course id
2857
     *
2858
     * @return int
2859
     */
2860
    public static function get_interactions_count_from_db($lp_iv_id, $course_id)
2861
    {
2862
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2863
        $lp_iv_id = (int) $lp_iv_id;
2864
        $course_id = (int) $course_id;
2865
2866
        $sql = "SELECT count(*) FROM $table
2867
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2868
        $res = Database::query($sql);
2869
        $num = 0;
2870
        if (Database::num_rows($res)) {
2871
            $row = Database::fetch_array($res);
2872
            $num = $row[0];
2873
        }
2874
2875
        return $num;
2876
    }
2877
2878
    /**
2879
     * Return the interactions as an array for the given lp_iv_id.
2880
     * This method can be used as static.
2881
     *
2882
     * @param int $lp_iv_id Learnpath Item View ID
2883
     *
2884
     * @return array
2885
     *
2886
     * @todo    Transcode labels instead of switching to HTML (which requires to know the encoding of the LP)
2887
     */
2888
    public static function get_iv_interactions_array($lp_iv_id, $course_id = 0)
2889
    {
2890
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2891
        $list = [];
2892
        $table = Database::get_course_table(TABLE_LP_IV_INTERACTION);
2893
        $lp_iv_id = (int) $lp_iv_id;
2894
2895
        if (empty($lp_iv_id) || empty($course_id)) {
2896
            return [];
2897
        }
2898
2899
        $sql = "SELECT * FROM $table
2900
                WHERE c_id = ".$course_id." AND lp_iv_id = $lp_iv_id
2901
                ORDER BY order_id ASC";
2902
        $res = Database::query($sql);
2903
        $num = Database::num_rows($res);
2904
        if ($num > 0) {
2905
            $list[] = [
2906
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
2907
                'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES),
2908
                'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES),
2909
                'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES),
2910
                'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES),
2911
                'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES),
2912
                'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES),
2913
                'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES),
2914
                'student_response_formatted' => '',
2915
            ];
2916
            while ($row = Database::fetch_array($res)) {
2917
                $studentResponseFormatted = urldecode($row['student_response']);
2918
                $content_student_response = explode('__|', $studentResponseFormatted);
2919
                if (count($content_student_response) > 0) {
2920
                    if (count($content_student_response) >= 3) {
2921
                        // Pop the element off the end of array.
2922
                        array_pop($content_student_response);
2923
                    }
2924
                    $studentResponseFormatted = implode(',', $content_student_response);
2925
                }
2926
2927
                $list[] = [
2928
                    'order_id' => $row['order_id'] + 1,
2929
                    'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that
2930
                    'type' => $row['interaction_type'],
2931
                    'time' => $row['completion_time'],
2932
                    'correct_responses' => '', // Hide correct responses from students.
2933
                    'student_response' => $row['student_response'],
2934
                    'result' => $row['result'],
2935
                    'latency' => $row['latency'],
2936
                    'student_response_formatted' => $studentResponseFormatted,
2937
                ];
2938
            }
2939
        }
2940
2941
        return $list;
2942
    }
2943
2944
    /**
2945
     * Return the number of objectives for the given learnpath Item View ID.
2946
     * This method can be used as static.
2947
     *
2948
     * @param int $lp_iv_id  Item View ID
2949
     * @param int $course_id Course ID
2950
     *
2951
     * @return int Number of objectives
2952
     */
2953
    public static function get_objectives_count_from_db($lp_iv_id, $course_id)
2954
    {
2955
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2956
        $course_id = (int) $course_id;
2957
        $lp_iv_id = (int) $lp_iv_id;
2958
        $sql = "SELECT count(*) FROM $table
2959
                WHERE c_id = $course_id AND lp_iv_id = $lp_iv_id";
2960
        //@todo seems that this always returns 0
2961
        $res = Database::query($sql);
2962
        $num = 0;
2963
        if (Database::num_rows($res)) {
2964
            $row = Database::fetch_array($res);
2965
            $num = $row[0];
2966
        }
2967
2968
        return $num;
2969
    }
2970
2971
    /**
2972
     * Return the objectives as an array for the given lp_iv_id.
2973
     * This method can be used as static.
2974
     *
2975
     * @param int $lpItemViewId Learnpath Item View ID
2976
     * @param int $course_id
2977
     *
2978
     * @return array
2979
     *
2980
     * @todo    Translate labels
2981
     */
2982
    public static function get_iv_objectives_array($lpItemViewId = 0, $course_id = 0)
2983
    {
2984
        $course_id = empty($course_id) ? api_get_course_int_id() : (int) $course_id;
2985
        $lpItemViewId = (int) $lpItemViewId;
2986
2987
        if (empty($course_id) || empty($lpItemViewId)) {
2988
            return [];
2989
        }
2990
2991
        $table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
2992
        $sql = "SELECT * FROM $table
2993
                WHERE c_id = $course_id AND lp_iv_id = $lpItemViewId
2994
                ORDER BY order_id ASC";
2995
        $res = Database::query($sql);
2996
        $num = Database::num_rows($res);
2997
        $list = [];
2998
        if ($num > 0) {
2999
            $list[] = [
3000
                'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES),
3001
                'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES),
3002
                'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES),
3003
                'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES),
3004
                'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES),
3005
                'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES),
3006
            ];
3007
            while ($row = Database::fetch_array($res)) {
3008
                $list[] = [
3009
                    'order_id' => $row['order_id'] + 1,
3010
                    'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F
3011
                    'score_raw' => $row['score_raw'],
3012
                    'score_max' => $row['score_max'],
3013
                    'score_min' => $row['score_min'],
3014
                    'status' => $row['status'],
3015
                ];
3016
            }
3017
        }
3018
3019
        return $list;
3020
    }
3021
3022
    /**
3023
     * Generate and return the table of contents for this learnpath. The (flat) table returned can be
3024
     * used by get_html_toc() to be ready to display.
3025
     *
3026
     * @return array TOC as a table with 4 elements per row: title, link, status and level
3027
     */
3028
    public function get_toc()
3029
    {
3030
        $toc = [];
3031
        foreach ($this->ordered_items as $item_id) {
3032
            // TODO: Change this link generation and use new function instead.
3033
            $toc[] = [
3034
                'id' => $item_id,
3035
                'title' => $this->items[$item_id]->get_title(),
3036
                'status' => $this->items[$item_id]->get_status(),
3037
                'level' => $this->items[$item_id]->get_level(),
3038
                'type' => $this->items[$item_id]->get_type(),
3039
                'description' => $this->items[$item_id]->get_description(),
3040
                'path' => $this->items[$item_id]->get_path(),
3041
                'parent' => $this->items[$item_id]->get_parent(),
3042
            ];
3043
        }
3044
3045
        return $toc;
3046
    }
3047
3048
    /**
3049
     * Returns the CSS class name associated with a given item status.
3050
     *
3051
     * @param $status string an item status
3052
     *
3053
     * @return string CSS class name
3054
     */
3055
    public static function getStatusCSSClassName($status)
3056
    {
3057
        if (array_key_exists($status, self::STATUS_CSS_CLASS_NAME)) {
3058
            return self::STATUS_CSS_CLASS_NAME[$status];
3059
        }
3060
3061
        return '';
3062
    }
3063
3064
    /**
3065
     * Generate the tree of contents for this learnpath as an associative array tree
3066
     * with keys id, title, status, type, description, path, parent_id, children
3067
     * (title and descriptions as secured)
3068
     * and clues for CSS class composition:
3069
     *  - booleans is_current, is_parent_of_current, is_chapter
3070
     *  - string status_css_class_name.
3071
     *
3072
     * @param $parentId int restrict returned list to children of this parent
3073
     *
3074
     * @return array TOC as a table
3075
     */
3076
    public function getTOCTree($parentId = 0)
3077
    {
3078
        $toc = [];
3079
        $currentItemId = $this->get_current_item_id();
3080
3081
        foreach ($this->ordered_items as $itemId) {
3082
            $item = $this->items[$itemId];
3083
            if ($item->get_parent() == $parentId) {
3084
                $title = $item->get_title();
3085
                if (empty($title)) {
3086
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $itemId);
3087
                }
3088
3089
                $itemData = [
3090
                    'id' => $itemId,
3091
                    'title' => Security::remove_XSS($title),
3092
                    'status' => $item->get_status(),
3093
                    'level' => $item->get_level(), // FIXME should not be needed
3094
                    'type' => $item->get_type(),
3095
                    'description' => Security::remove_XSS($item->get_description()),
3096
                    'path' => $item->get_path(),
3097
                    'parent_id' => $item->get_parent(),
3098
                    'children' => $this->getTOCTree($itemId),
3099
                    'is_current' => ($itemId == $currentItemId),
3100
                    'is_parent_of_current' => false,
3101
                    'is_chapter' => in_array($item->get_type(), self::getChapterTypes()),
3102
                    'status_css_class_name' => $this->getStatusCSSClassName($item->get_status()),
3103
                    'current_id' => $currentItemId, // FIXME should not be needed, not a property of item
3104
                ];
3105
3106
                if (!empty($itemData['children'])) {
3107
                    foreach ($itemData['children'] as $child) {
3108
                        if ($child['is_current'] || $child['is_parent_of_current']) {
3109
                            $itemData['is_parent_of_current'] = true;
3110
                            break;
3111
                        }
3112
                    }
3113
                }
3114
3115
                $toc[] = $itemData;
3116
            }
3117
        }
3118
3119
        return $toc;
3120
    }
3121
3122
    /**
3123
     * Generate and return the table of contents for this learnpath. The JS
3124
     * table returned is used inside of scorm_api.php.
3125
     *
3126
     * @param string $varname
3127
     *
3128
     * @return string A JS array variable construction
3129
     */
3130
    public function get_items_details_as_js($varname = 'olms.lms_item_types')
3131
    {
3132
        $toc = $varname.' = new Array();';
3133
        foreach ($this->ordered_items as $item_id) {
3134
            $toc .= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';";
3135
        }
3136
3137
        return $toc;
3138
    }
3139
3140
    /**
3141
     * Gets the learning path type.
3142
     *
3143
     * @param bool $get_name Return the name? If false, return the ID. Default is false.
3144
     *
3145
     * @return mixed Type ID or name, depending on the parameter
3146
     */
3147
    public function get_type($get_name = false)
3148
    {
3149
        $res = false;
3150
        if (!empty($this->type) && (!$get_name)) {
3151
            $res = $this->type;
3152
        }
3153
3154
        return $res;
3155
    }
3156
3157
    /**
3158
     * Gets the learning path type as static method.
3159
     *
3160
     * @param int $lp_id
3161
     *
3162
     * @return mixed Type ID or name, depending on the parameter
3163
     */
3164
    public static function get_type_static($lp_id = 0)
3165
    {
3166
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
3167
        $lp_id = (int) $lp_id;
3168
        $sql = "SELECT lp_type FROM $tbl_lp
3169
                WHERE iid = $lp_id";
3170
        $res = Database::query($sql);
3171
        if ($res === false) {
3172
            return null;
3173
        }
3174
        if (Database::num_rows($res) <= 0) {
3175
            return null;
3176
        }
3177
        $row = Database::fetch_array($res);
3178
3179
        return $row['lp_type'];
3180
    }
3181
3182
    /**
3183
     * Gets a flat list of item IDs ordered for display (level by level ordered by order_display)
3184
     * This method can be used as abstract and is recursive.
3185
     *
3186
     * @param int $lp        Learnpath ID
3187
     * @param int $parent    Parent ID of the items to look for
3188
     * @param int $course_id
3189
     *
3190
     * @return array Ordered list of item IDs (empty array on error)
3191
     */
3192
    public static function get_flat_ordered_items_list($lp = 1, $parent = 0, $course_id = 0)
3193
    {
3194
        if (empty($course_id)) {
3195
            $course_id = api_get_course_int_id();
3196
        } else {
3197
            $course_id = (int) $course_id;
3198
        }
3199
        $list = [];
3200
3201
        if (empty($lp)) {
3202
            return $list;
3203
        }
3204
3205
        $lp = (int) $lp;
3206
        $parent = (int) $parent;
3207
3208
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3209
        $sql = "SELECT iid FROM $tbl_lp_item
3210
                WHERE c_id = $course_id AND lp_id = $lp AND parent_item_id = $parent
3211
                ORDER BY display_order";
3212
3213
        $res = Database::query($sql);
3214
        while ($row = Database::fetch_array($res)) {
3215
            $sublist = self::get_flat_ordered_items_list(
3216
                $lp,
3217
                $row['iid'],
3218
                $course_id
3219
            );
3220
            $list[] = $row['iid'];
3221
            foreach ($sublist as $item) {
3222
                $list[] = $item;
3223
            }
3224
        }
3225
3226
        return $list;
3227
    }
3228
3229
    /**
3230
     * @return array
3231
     */
3232
    public static function getChapterTypes()
3233
    {
3234
        return [
3235
            'dir',
3236
        ];
3237
    }
3238
3239
    /**
3240
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3241
     *
3242
     * @param $tree
3243
     *
3244
     * @return array HTML TOC ready to display
3245
     */
3246
    public function getParentToc($tree)
3247
    {
3248
        if (empty($tree)) {
3249
            $tree = $this->get_toc();
3250
        }
3251
        $dirTypes = self::getChapterTypes();
3252
        $myCurrentId = $this->get_current_item_id();
3253
        $listParent = [];
3254
        $listChildren = [];
3255
        $listNotParent = [];
3256
        $list = [];
3257
        foreach ($tree as $subtree) {
3258
            if (in_array($subtree['type'], $dirTypes)) {
3259
                $listChildren = $this->getChildrenToc($tree, $subtree['id']);
3260
                $subtree['children'] = $listChildren;
3261
                if (!empty($subtree['children'])) {
3262
                    foreach ($subtree['children'] as $subItem) {
3263
                        if ($subItem['id'] == $this->current) {
3264
                            $subtree['parent_current'] = 'in';
3265
                            $subtree['current'] = 'on';
3266
                        }
3267
                    }
3268
                }
3269
                $listParent[] = $subtree;
3270
            }
3271
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == null) {
3272
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3273
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3274
                }
3275
3276
                $title = Security::remove_XSS($subtree['title']);
3277
                unset($subtree['title']);
3278
3279
                if (empty($title)) {
3280
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3281
                }
3282
                $classStyle = null;
3283
                if ($subtree['id'] == $this->current) {
3284
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3285
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3286
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3287
                }
3288
                $subtree['title'] = $title;
3289
                $subtree['class'] = $classStyle.' '.$cssStatus;
3290
                $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3291
                $subtree['current_id'] = $myCurrentId;
3292
                $listNotParent[] = $subtree;
3293
            }
3294
        }
3295
3296
        $list['are_parents'] = $listParent;
3297
        $list['not_parents'] = $listNotParent;
3298
3299
        return $list;
3300
    }
3301
3302
    /**
3303
     * Uses the table generated by get_toc() and returns an HTML-formattedstring ready to display.
3304
     *
3305
     * @param array $tree
3306
     * @param int   $id
3307
     * @param bool  $parent
3308
     *
3309
     * @return array HTML TOC ready to display
3310
     */
3311
    public function getChildrenToc($tree, $id, $parent = true)
3312
    {
3313
        if (empty($tree)) {
3314
            $tree = $this->get_toc();
3315
        }
3316
3317
        $dirTypes = self::getChapterTypes();
3318
        $currentItemId = $this->get_current_item_id();
3319
        $list = [];
3320
3321
        foreach ($tree as $subtree) {
3322
            $subtree['tree'] = null;
3323
3324
            if (!in_array($subtree['type'], $dirTypes) && $subtree['parent'] == $id) {
3325
                if ($subtree['id'] == $this->current) {
3326
                    $subtree['current'] = 'active';
3327
                } else {
3328
                    $subtree['current'] = null;
3329
                }
3330
                if (array_key_exists($subtree['status'], self::STATUS_CSS_CLASS_NAME)) {
3331
                    $cssStatus = self::STATUS_CSS_CLASS_NAME[$subtree['status']];
3332
                }
3333
3334
                $title = Security::remove_XSS($subtree['title']);
3335
                unset($subtree['title']);
3336
                if (empty($title)) {
3337
                    $title = self::rl_get_resource_name(api_get_course_id(), $this->get_id(), $subtree['id']);
3338
                }
3339
3340
                $classStyle = null;
3341
                if ($subtree['id'] == $this->current) {
3342
                    $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3343
                } elseif (!in_array($subtree['type'], $dirTypes)) {
3344
                    $classStyle = 'scorm_item_normal '.$classStyle.' ';
3345
                }
3346
3347
                if (in_array($subtree['type'], $dirTypes)) {
3348
                    $subtree['title'] = stripslashes($title);
3349
                } else {
3350
                    $subtree['title'] = $title;
3351
                    $subtree['class'] = $classStyle.' '.$cssStatus;
3352
                    $subtree['url'] = $this->get_link('http', $subtree['id'], $tree);
3353
                    $subtree['current_id'] = $currentItemId;
3354
                }
3355
                $list[] = $subtree;
3356
            }
3357
        }
3358
3359
        return $list;
3360
    }
3361
3362
    /**
3363
     * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display.
3364
     *
3365
     * @param array $toc_list
3366
     *
3367
     * @return array HTML TOC ready to display
3368
     */
3369
    public function getListArrayToc($toc_list = [])
3370
    {
3371
        if (empty($toc_list)) {
3372
            $toc_list = $this->get_toc();
3373
        }
3374
        // Temporary variables.
3375
        $currentItemId = $this->get_current_item_id();
3376
        $list = [];
3377
        $arrayList = [];
3378
3379
        foreach ($toc_list as $item) {
3380
            $list['id'] = $item['id'];
3381
            $list['status'] = $item['status'];
3382
            $cssStatus = null;
3383
3384
            if (array_key_exists($item['status'], self::STATUS_CSS_CLASS_NAME)) {
3385
                $cssStatus = self::STATUS_CSS_CLASS_NAME[$item['status']];
3386
            }
3387
3388
            $classStyle = ' ';
3389
            $dirTypes = self::getChapterTypes();
3390
3391
            if (in_array($item['type'], $dirTypes)) {
3392
                $classStyle = 'scorm_item_section ';
3393
            }
3394
            if ($item['id'] == $this->current) {
3395
                $classStyle = 'scorm_item_normal '.$classStyle.'scorm_highlight';
3396
            } elseif (!in_array($item['type'], $dirTypes)) {
3397
                $classStyle = 'scorm_item_normal '.$classStyle.' ';
3398
            }
3399
            $title = $item['title'];
3400
            if (empty($title)) {
3401
                $title = self::rl_get_resource_name(
3402
                    api_get_course_id(),
3403
                    $this->get_id(),
3404
                    $item['id']
3405
                );
3406
            }
3407
            $title = Security::remove_XSS($item['title']);
3408
3409
            if (empty($item['description'])) {
3410
                $list['description'] = $title;
3411
            } else {
3412
                $list['description'] = $item['description'];
3413
            }
3414
3415
            $list['class'] = $classStyle.' '.$cssStatus;
3416
            $list['level'] = $item['level'];
3417
            $list['type'] = $item['type'];
3418
3419
            if (in_array($item['type'], $dirTypes)) {
3420
                $list['css_level'] = 'level_'.$item['level'];
3421
            } else {
3422
                $list['css_level'] = 'level_'.$item['level'].' scorm_type_'.self::format_scorm_type_item($item['type']);
3423
            }
3424
3425
            if (in_array($item['type'], $dirTypes)) {
3426
                $list['title'] = stripslashes($title);
3427
            } else {
3428
                $list['title'] = stripslashes($title);
3429
                $list['url'] = $this->get_link('http', $item['id'], $toc_list);
3430
                $list['current_id'] = $currentItemId;
3431
            }
3432
            $arrayList[] = $list;
3433
        }
3434
3435
        return $arrayList;
3436
    }
3437
3438
    /**
3439
     * Returns an HTML-formatted string ready to display with teacher buttons
3440
     * in LP view menu.
3441
     *
3442
     * @return string HTML TOC ready to display
3443
     */
3444
    public function get_teacher_toc_buttons()
3445
    {
3446
        $isAllow = api_is_allowed_to_edit(null, true, false, false);
3447
        $hideIcons = api_get_configuration_value('hide_teacher_icons_lp');
3448
        $html = '';
3449
        if ($isAllow && $hideIcons == false) {
3450
            if ($this->get_lp_session_id() == api_get_session_id()) {
3451
                $html .= '<div id="actions_lp" class="actions_lp"><hr>';
3452
                $html .= '<div class="btn-group">';
3453
                $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'>".
3454
                    Display::returnFontAwesomeIcon('street-view').get_lang('Overview')."</a>";
3455
                $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'>".
3456
                    Display::returnFontAwesomeIcon('pencil').get_lang('Edit')."</a>";
3457
                $html .= '<a class="btn btn-sm btn-default" href="lp_controller.php?'.api_get_cidreq()."&action=edit&lp_id=".$this->lp_id.'&isStudentView=false">'.
3458
                    Display::returnFontAwesomeIcon('cog').get_lang('Settings').'</a>';
3459
                $html .= '</div>';
3460
                $html .= '</div>';
3461
            }
3462
        }
3463
3464
        return $html;
3465
    }
3466
3467
    /**
3468
     * Gets the learnpath maker name - generally the editor's name.
3469
     *
3470
     * @return string Learnpath maker name
3471
     */
3472
    public function get_maker()
3473
    {
3474
        if (!empty($this->maker)) {
3475
            return $this->maker;
3476
        }
3477
3478
        return '';
3479
    }
3480
3481
    /**
3482
     * Gets the learnpath name/title.
3483
     *
3484
     * @return string Learnpath name/title
3485
     */
3486
    public function get_name()
3487
    {
3488
        if (!empty($this->name)) {
3489
            return $this->name;
3490
        }
3491
3492
        return 'N/A';
3493
    }
3494
3495
    /**
3496
     * @return string
3497
     */
3498
    public function getNameNoTags()
3499
    {
3500
        return strip_tags($this->get_name());
3501
    }
3502
3503
    /**
3504
     * Gets a link to the resource from the present location, depending on item ID.
3505
     *
3506
     * @param string $type         Type of link expected
3507
     * @param int    $item_id      Learnpath item ID
3508
     * @param bool   $provided_toc
3509
     *
3510
     * @return string $provided_toc Link to the lp_item resource
3511
     */
3512
    public function get_link($type = 'http', $item_id = 0, $provided_toc = false)
3513
    {
3514
        $course_id = $this->get_course_int_id();
3515
        $item_id = (int) $item_id;
3516
3517
        if (empty($item_id)) {
3518
            $item_id = $this->get_current_item_id();
3519
3520
            if (empty($item_id)) {
3521
                //still empty, this means there was no item_id given and we are not in an object context or
3522
                //the object property is empty, return empty link
3523
                $this->first();
3524
3525
                return '';
3526
            }
3527
        }
3528
3529
        $file = '';
3530
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
3531
        $lp_item_table = Database::get_course_table(TABLE_LP_ITEM);
3532
        $lp_item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW);
3533
3534
        $sql = "SELECT
3535
                    l.lp_type as ltype,
3536
                    l.path as lpath,
3537
                    li.item_type as litype,
3538
                    li.path as lipath,
3539
                    li.parameters as liparams
3540
        		FROM $lp_table l
3541
                INNER JOIN $lp_item_table li
3542
                ON (li.lp_id = l.iid)
3543
        		WHERE
3544
        		    li.iid = $item_id
3545
        		";
3546
        $res = Database::query($sql);
3547
        if (Database::num_rows($res) > 0) {
3548
            $row = Database::fetch_array($res);
3549
            $lp_type = $row['ltype'];
3550
            $lp_path = $row['lpath'];
3551
            $lp_item_type = $row['litype'];
3552
            $lp_item_path = $row['lipath'];
3553
            $lp_item_params = $row['liparams'];
3554
3555
            if (empty($lp_item_params) && strpos($lp_item_path, '?') !== false) {
3556
                list($lp_item_path, $lp_item_params) = explode('?', $lp_item_path);
3557
            }
3558
            $sys_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
3559
            if ($type === 'http') {
3560
                //web path
3561
                $course_path = api_get_path(WEB_COURSE_PATH).api_get_course_path();
3562
            } else {
3563
                $course_path = $sys_course_path; //system path
3564
            }
3565
3566
            // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc),
3567
            // then change the lp type to thread it as a normal Chamilo LP not a SCO.
3568
            if (in_array(
3569
                $lp_item_type,
3570
                ['quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication']
3571
            )
3572
            ) {
3573
                $lp_type = 1;
3574
            }
3575
3576
            // Now go through the specific cases to get the end of the path
3577
            // @todo Use constants instead of int values.
3578
            switch ($lp_type) {
3579
                case 1:
3580
                    $file = self::rl_get_resource_link_for_learnpath(
3581
                        $course_id,
3582
                        $this->get_id(),
3583
                        $item_id,
3584
                        $this->get_view_id(),
3585
                        $this->get_lp_session_id()
3586
                    );
3587
                    switch ($lp_item_type) {
3588
                        case 'document':
3589
                            // Shows a button to download the file instead of just downloading the file directly.
3590
                            $documentPathInfo = pathinfo($file);
3591
                            if (isset($documentPathInfo['extension'])) {
3592
                                $parsed = parse_url($documentPathInfo['extension']);
3593
                                if (isset($parsed['path'])) {
3594
                                    $extension = $parsed['path'];
3595
                                    $extensionsToDownload = [
3596
                                        'zip',
3597
                                        'ppt',
3598
                                        'pptx',
3599
                                        'ods',
3600
                                        'xlsx',
3601
                                        'xls',
3602
                                        'csv',
3603
                                        'doc',
3604
                                        'docx',
3605
                                        'dot',
3606
                                    ];
3607
3608
                                    if (in_array($extension, $extensionsToDownload)) {
3609
                                        $file = api_get_path(WEB_CODE_PATH).
3610
                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
3611
                                    }
3612
                                }
3613
                            }
3614
                            break;
3615
                        case 'dir':
3616
                            $file = 'lp_content.php?type=dir';
3617
                            break;
3618
                        case 'link':
3619
                            if (Link::is_youtube_link($file)) {
3620
                                $src = Link::get_youtube_video_id($file);
3621
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=youtube&source='.$src;
3622
                            } elseif (Link::isVimeoLink($file)) {
3623
                                $src = Link::getVimeoLinkId($file);
3624
                                $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=vimeo&source='.$src;
3625
                            } else {
3626
                                // If the current site is HTTPS and the link is
3627
                                // HTTP, browsers will refuse opening the link
3628
                                $urlId = api_get_current_access_url_id();
3629
                                $url = api_get_access_url($urlId, false);
3630
                                $protocol = substr($url['url'], 0, 5);
3631
                                if ($protocol === 'https') {
3632
                                    $linkProtocol = substr($file, 0, 5);
3633
                                    if ($linkProtocol === 'http:') {
3634
                                        //this is the special intervention case
3635
                                        $file = api_get_path(WEB_CODE_PATH).'lp/embed.php?type=nonhttps&source='.urlencode($file);
3636
                                    }
3637
                                }
3638
                            }
3639
                            break;
3640
                        case 'quiz':
3641
                            // Check how much attempts of a exercise exits in lp
3642
                            $lp_item_id = $this->get_current_item_id();
3643
                            $lp_view_id = $this->get_view_id();
3644
3645
                            $prevent_reinit = null;
3646
                            if (isset($this->items[$this->current])) {
3647
                                $prevent_reinit = $this->items[$this->current]->get_prevent_reinit();
3648
                            }
3649
3650
                            if (empty($provided_toc)) {
3651
                                if ($this->debug > 0) {
3652
                                    error_log('In learnpath::get_link() Loading get_toc ', 0);
3653
                                }
3654
                                $list = $this->get_toc();
3655
                            } else {
3656
                                if ($this->debug > 0) {
3657
                                    error_log('In learnpath::get_link() Loading get_toc from "cache" ', 0);
3658
                                }
3659
                                $list = $provided_toc;
3660
                            }
3661
3662
                            $type_quiz = false;
3663
                            foreach ($list as $toc) {
3664
                                if ($toc['id'] == $lp_item_id && $toc['type'] === 'quiz') {
3665
                                    $type_quiz = true;
3666
                                }
3667
                            }
3668
3669
                            if ($type_quiz) {
3670
                                $lp_item_id = (int) $lp_item_id;
3671
                                $lp_view_id = (int) $lp_view_id;
3672
                                $sql = "SELECT count(*) FROM $lp_item_view_table
3673
                                        WHERE
3674
                                            c_id = $course_id AND
3675
                                            lp_item_id='".$lp_item_id."' AND
3676
                                            lp_view_id ='".$lp_view_id."' AND
3677
                                            status='completed'";
3678
                                $result = Database::query($sql);
3679
                                $row_count = Database:: fetch_row($result);
3680
                                $count_item_view = (int) $row_count[0];
3681
                                $not_multiple_attempt = 0;
3682
                                if ($prevent_reinit === 1 && $count_item_view > 0) {
3683
                                    $not_multiple_attempt = 1;
3684
                                }
3685
                                $file .= '&not_multiple_attempt='.$not_multiple_attempt;
3686
                            }
3687
                            break;
3688
                    }
3689
3690
                    $tmp_array = explode('/', $file);
3691
                    $document_name = $tmp_array[count($tmp_array) - 1];
3692
                    if (strpos($document_name, '_DELETED_')) {
3693
                        $file = 'blank.php?error=document_deleted';
3694
                    }
3695
                    break;
3696
                case 2:
3697
                    if ($this->debug > 2) {
3698
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3699
                    }
3700
3701
                    if ($lp_item_type != 'dir') {
3702
                        // Quite complex here:
3703
                        // We want to make sure 'http://' (and similar) links can
3704
                        // be loaded as is (withouth the Chamilo path in front) but
3705
                        // some contents use this form: resource.htm?resource=http://blablabla
3706
                        // which means we have to find a protocol at the path's start, otherwise
3707
                        // it should not be considered as an external URL.
3708
                        // if ($this->prerequisites_match($item_id)) {
3709
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3710
                            if ($this->debug > 2) {
3711
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3712
                            }
3713
                            // Distant url, return as is.
3714
                            $file = $lp_item_path;
3715
                        } else {
3716
                            if ($this->debug > 2) {
3717
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3718
                            }
3719
                            // Prevent getting untranslatable urls.
3720
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3721
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3722
                            // Prepare the path.
3723
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3724
                            // TODO: Fix this for urls with protocol header.
3725
                            $file = str_replace('//', '/', $file);
3726
                            $file = str_replace(':/', '://', $file);
3727
                            if (substr($lp_path, -1) == '/') {
3728
                                $lp_path = substr($lp_path, 0, -1);
3729
                            }
3730
3731
                            if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$lp_item_path))) {
3732
                                // if file not found.
3733
                                $decoded = html_entity_decode($lp_item_path);
3734
                                list($decoded) = explode('?', $decoded);
3735
                                if (!is_file(realpath($sys_course_path.'/scorm/'.$lp_path.'/'.$decoded))) {
3736
                                    $file = self::rl_get_resource_link_for_learnpath(
3737
                                        $course_id,
3738
                                        $this->get_id(),
3739
                                        $item_id,
3740
                                        $this->get_view_id()
3741
                                    );
3742
                                    if (empty($file)) {
3743
                                        $file = 'blank.php?error=document_not_found';
3744
                                    } else {
3745
                                        $tmp_array = explode('/', $file);
3746
                                        $document_name = $tmp_array[count($tmp_array) - 1];
3747
                                        if (strpos($document_name, '_DELETED_')) {
3748
                                            $file = 'blank.php?error=document_deleted';
3749
                                        } else {
3750
                                            $file = 'blank.php?error=document_not_found';
3751
                                        }
3752
                                    }
3753
                                } else {
3754
                                    $file = $course_path.'/scorm/'.$lp_path.'/'.$decoded;
3755
                                }
3756
                            }
3757
                        }
3758
3759
                        // We want to use parameters if they were defined in the imsmanifest
3760
                        if (strpos($file, 'blank.php') === false) {
3761
                            $lp_item_params = ltrim($lp_item_params, '?');
3762
                            $file .= (strstr($file, '?') === false ? '?' : '').$lp_item_params;
3763
                        }
3764
                    } else {
3765
                        $file = 'lp_content.php?type=dir';
3766
                    }
3767
                    break;
3768
                case 3:
3769
                    if ($this->debug > 2) {
3770
                        error_log('In learnpath::get_link() '.__LINE__.' - Item type: '.$lp_item_type, 0);
3771
                    }
3772
                    // Formatting AICC HACP append URL.
3773
                    $aicc_append = '?aicc_sid='.urlencode(session_id()).'&aicc_url='.urlencode(api_get_path(WEB_CODE_PATH).'lp/aicc_hacp.php').'&';
3774
                    if (!empty($lp_item_params)) {
3775
                        $aicc_append .= $lp_item_params.'&';
3776
                    }
3777
                    if ($lp_item_type != 'dir') {
3778
                        // Quite complex here:
3779
                        // We want to make sure 'http://' (and similar) links can
3780
                        // be loaded as is (withouth the Chamilo path in front) but
3781
                        // some contents use this form: resource.htm?resource=http://blablabla
3782
                        // which means we have to find a protocol at the path's start, otherwise
3783
                        // it should not be considered as an external URL.
3784
                        if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) {
3785
                            if ($this->debug > 2) {
3786
                                error_log('In learnpath::get_link() '.__LINE__.' - Found match for protocol in '.$lp_item_path, 0);
3787
                            }
3788
                            // Distant url, return as is.
3789
                            $file = $lp_item_path;
3790
                            // Enabled and modified by Ivan Tcholakov, 16-OCT-2008.
3791
                            /*
3792
                            if (stristr($file,'<servername>') !== false) {
3793
                                $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path);
3794
                            }
3795
                            */
3796
                            if (stripos($file, '<servername>') !== false) {
3797
                                //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path);
3798
                                $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path));
3799
                                $file = str_replace('<servername>', $web_course_path.'/scorm/'.$lp_path, $lp_item_path);
3800
                            }
3801
3802
                            $file .= $aicc_append;
3803
                        } else {
3804
                            if ($this->debug > 2) {
3805
                                error_log('In learnpath::get_link() '.__LINE__.' - No starting protocol in '.$lp_item_path, 0);
3806
                            }
3807
                            // Prevent getting untranslatable urls.
3808
                            $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path);
3809
                            $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path);
3810
                            // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name.
3811
                            $file = $course_path.'/scorm/'.$lp_path.'/'.$lp_item_path;
3812
                            // TODO: Fix this for urls with protocol header.
3813
                            $file = str_replace('//', '/', $file);
3814
                            $file = str_replace(':/', '://', $file);
3815
                            $file .= $aicc_append;
3816
                        }
3817
                    } else {
3818
                        $file = 'lp_content.php?type=dir';
3819
                    }
3820
                    break;
3821
                case 4:
3822
                    break;
3823
                default:
3824
                    break;
3825
            }
3826
            // Replace &amp; by & because &amp; will break URL with params
3827
            $file = !empty($file) ? str_replace('&amp;', '&', $file) : '';
3828
        }
3829
        if ($this->debug > 2) {
3830
            error_log('In learnpath::get_link() - returning "'.$file.'" from get_link', 0);
3831
        }
3832
3833
        return $file;
3834
    }
3835
3836
    /**
3837
     * Gets the latest usable view or generate a new one.
3838
     *
3839
     * @param int $attempt_num Optional attempt number. If none given, takes the highest from the lp_view table
3840
     * @param int $userId      The user ID, as $this->get_user_id() is not always available
3841
     *
3842
     * @return int DB lp_view id
3843
     */
3844
    public function get_view($attempt_num = 0, $userId = null)
3845
    {
3846
        $search = '';
3847
        // Use $attempt_num to enable multi-views management (disabled so far).
3848
        if ($attempt_num != 0 && intval(strval($attempt_num)) == $attempt_num) {
3849
            $search = 'AND view_count = '.$attempt_num;
3850
        }
3851
        // When missing $attempt_num, search for a unique lp_view record for this lp and user.
3852
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
3853
3854
        $course_id = api_get_course_int_id();
3855
        $sessionId = api_get_session_id();
3856
3857
        // Check user ID.
3858
        if (empty($userId)) {
3859
            if (empty($this->get_user_id())) {
3860
                $this->error = 'User ID is empty in learnpath::get_view()';
3861
3862
                return null;
3863
            } else {
3864
                $userId = $this->get_user_id();
3865
            }
3866
        }
3867
3868
        $sql = "SELECT iid, view_count FROM $lp_view_table
3869
        		WHERE
3870
        		    c_id = $course_id AND
3871
        		    lp_id = ".$this->get_id()." AND
3872
        		    user_id = ".$userId." AND
3873
        		    session_id = $sessionId
3874
        		    $search
3875
                ORDER BY view_count DESC";
3876
        $res = Database::query($sql);
3877
        if (Database::num_rows($res) > 0) {
3878
            $row = Database::fetch_array($res);
3879
            $this->lp_view_id = $row['iid'];
3880
        } elseif (!api_is_invitee()) {
3881
            // There is no database record, create one.
3882
            $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id) VALUES
3883
            		($course_id, ".$this->get_id().",".$this->get_user_id().", 1, $sessionId)";
3884
            Database::query($sql);
3885
            $id = Database::insert_id();
3886
            $this->lp_view_id = $id;
3887
3888
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $id";
3889
            Database::query($sql);
3890
        }
3891
3892
        return $this->lp_view_id;
3893
    }
3894
3895
    /**
3896
     * Gets the current view id.
3897
     *
3898
     * @return int View ID (from lp_view)
3899
     */
3900
    public function get_view_id()
3901
    {
3902
        if (!empty($this->lp_view_id)) {
3903
            return (int) $this->lp_view_id;
3904
        }
3905
3906
        return 0;
3907
    }
3908
3909
    /**
3910
     * Gets the update queue.
3911
     *
3912
     * @return array Array containing IDs of items to be updated by JavaScript
3913
     */
3914
    public function get_update_queue()
3915
    {
3916
        return $this->update_queue;
3917
    }
3918
3919
    /**
3920
     * Gets the user ID.
3921
     *
3922
     * @return int User ID
3923
     */
3924
    public function get_user_id()
3925
    {
3926
        if (!empty($this->user_id)) {
3927
            return (int) $this->user_id;
3928
        }
3929
3930
        return false;
3931
    }
3932
3933
    /**
3934
     * Checks if any of the items has an audio element attached.
3935
     *
3936
     * @return bool True or false
3937
     */
3938
    public function has_audio()
3939
    {
3940
        $has = false;
3941
        foreach ($this->items as $i => $item) {
3942
            if (!empty($this->items[$i]->audio)) {
3943
                $has = true;
3944
                break;
3945
            }
3946
        }
3947
3948
        return $has;
3949
    }
3950
3951
    /**
3952
     * Moves an item up and down at its level.
3953
     *
3954
     * @param int    $id        Item to move up and down
3955
     * @param string $direction Direction 'up' or 'down'
3956
     *
3957
     * @return bool|int
3958
     */
3959
    public function move_item($id, $direction)
3960
    {
3961
        $course_id = api_get_course_int_id();
3962
        if (empty($id) || empty($direction)) {
3963
            return false;
3964
        }
3965
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
3966
        $sql_sel = "SELECT *
3967
                    FROM $tbl_lp_item
3968
                    WHERE
3969
                        iid = $id
3970
                    ";
3971
        $res_sel = Database::query($sql_sel);
3972
        // Check if elem exists.
3973
        if (Database::num_rows($res_sel) < 1) {
3974
            return false;
3975
        }
3976
        // Gather data.
3977
        $row = Database::fetch_array($res_sel);
3978
        $previous = $row['previous_item_id'];
3979
        $next = $row['next_item_id'];
3980
        $display = $row['display_order'];
3981
        $parent = $row['parent_item_id'];
3982
        $lp = $row['lp_id'];
3983
        // Update the item (switch with previous/next one).
3984
        switch ($direction) {
3985
            case 'up':
3986
                if ($display > 1) {
3987
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
3988
                                 WHERE iid = $previous";
3989
                    $res_sel2 = Database::query($sql_sel2);
3990
                    if (Database::num_rows($res_sel2) < 1) {
3991
                        $previous_previous = 0;
3992
                    }
3993
                    // Gather data.
3994
                    $row2 = Database::fetch_array($res_sel2);
3995
                    $previous_previous = $row2['previous_item_id'];
3996
                    // Update previous_previous item (switch "next" with current).
3997
                    if ($previous_previous != 0) {
3998
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
3999
                                        next_item_id = $id
4000
                                    WHERE iid = $previous_previous";
4001
                        Database::query($sql_upd2);
4002
                    }
4003
                    // Update previous item (switch with current).
4004
                    if ($previous != 0) {
4005
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4006
                                    next_item_id = $next,
4007
                                    previous_item_id = $id,
4008
                                    display_order = display_order +1
4009
                                    WHERE iid = $previous";
4010
                        Database::query($sql_upd2);
4011
                    }
4012
4013
                    // Update current item (switch with previous).
4014
                    if ($id != 0) {
4015
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4016
                                        next_item_id = $previous,
4017
                                        previous_item_id = $previous_previous,
4018
                                        display_order = display_order-1
4019
                                    WHERE c_id = ".$course_id." AND id = $id";
4020
                        Database::query($sql_upd2);
4021
                    }
4022
                    // Update next item (new previous item).
4023
                    if (!empty($next)) {
4024
                        $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous
4025
                                     WHERE iid = $next";
4026
                        Database::query($sql_upd2);
4027
                    }
4028
                    $display = $display - 1;
4029
                }
4030
                break;
4031
            case 'down':
4032
                if ($next != 0) {
4033
                    $sql_sel2 = "SELECT * FROM $tbl_lp_item
4034
                                 WHERE iid = $next";
4035
                    $res_sel2 = Database::query($sql_sel2);
4036
                    if (Database::num_rows($res_sel2) < 1) {
4037
                        $next_next = 0;
4038
                    }
4039
                    // Gather data.
4040
                    $row2 = Database::fetch_array($res_sel2);
4041
                    $next_next = $row2['next_item_id'];
4042
                    // Update previous item (switch with current).
4043
                    if ($previous != 0) {
4044
                        $sql_upd2 = "UPDATE $tbl_lp_item
4045
                                     SET next_item_id = $next
4046
                                     WHERE iid = $previous";
4047
                        Database::query($sql_upd2);
4048
                    }
4049
                    // Update current item (switch with previous).
4050
                    if ($id != 0) {
4051
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4052
                                     previous_item_id = $next,
4053
                                     next_item_id = $next_next,
4054
                                     display_order = display_order + 1
4055
                                     WHERE iid = $id";
4056
                        Database::query($sql_upd2);
4057
                    }
4058
4059
                    // Update next item (new previous item).
4060
                    if ($next != 0) {
4061
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4062
                                     previous_item_id = $previous,
4063
                                     next_item_id = $id,
4064
                                     display_order = display_order-1
4065
                                     WHERE iid = $next";
4066
                        Database::query($sql_upd2);
4067
                    }
4068
4069
                    // Update next_next item (switch "previous" with current).
4070
                    if ($next_next != 0) {
4071
                        $sql_upd2 = "UPDATE $tbl_lp_item SET
4072
                                     previous_item_id = $id
4073
                                     WHERE iid = $next_next";
4074
                        Database::query($sql_upd2);
4075
                    }
4076
                    $display = $display + 1;
4077
                }
4078
                break;
4079
            default:
4080
                return false;
4081
        }
4082
4083
        return $display;
4084
    }
4085
4086
    /**
4087
     * Move a LP up (display_order).
4088
     *
4089
     * @param int $lp_id      Learnpath ID
4090
     * @param int $categoryId Category ID
4091
     *
4092
     * @return bool
4093
     */
4094
    public static function move_up($lp_id, $categoryId = 0)
4095
    {
4096
        $courseId = api_get_course_int_id();
4097
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4098
4099
        $categoryCondition = '';
4100
        if (!empty($categoryId)) {
4101
            $categoryId = (int) $categoryId;
4102
            $categoryCondition = " AND category_id = $categoryId";
4103
        }
4104
        $sql = "SELECT * FROM $lp_table
4105
                WHERE c_id = $courseId
4106
                $categoryCondition
4107
                ORDER BY display_order";
4108
        $res = Database::query($sql);
4109
        if ($res === false) {
4110
            return false;
4111
        }
4112
4113
        $lps = [];
4114
        $lp_order = [];
4115
        $num = Database::num_rows($res);
4116
        // First check the order is correct, globally (might be wrong because
4117
        // of versions < 1.8.4)
4118
        if ($num > 0) {
4119
            $i = 1;
4120
            while ($row = Database::fetch_array($res)) {
4121
                if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it.
4122
                    $sql = "UPDATE $lp_table SET display_order = $i
4123
                            WHERE iid = ".$row['iid'];
4124
                    Database::query($sql);
4125
                }
4126
                $row['display_order'] = $i;
4127
                $lps[$row['iid']] = $row;
4128
                $lp_order[$i] = $row['iid'];
4129
                $i++;
4130
            }
4131
        }
4132
        if ($num > 1) { // If there's only one element, no need to sort.
4133
            $order = $lps[$lp_id]['display_order'];
4134
            if ($order > 1) { // If it's the first element, no need to move up.
4135
                $sql = "UPDATE $lp_table SET display_order = $order
4136
                        WHERE iid = ".$lp_order[$order - 1];
4137
                Database::query($sql);
4138
                $sql = "UPDATE $lp_table SET display_order = ".($order - 1)."
4139
                        WHERE iid = $lp_id";
4140
                Database::query($sql);
4141
            }
4142
        }
4143
4144
        return true;
4145
    }
4146
4147
    /**
4148
     * Move a learnpath down (display_order).
4149
     *
4150
     * @param int $lp_id      Learnpath ID
4151
     * @param int $categoryId Category ID
4152
     *
4153
     * @return bool
4154
     */
4155
    public static function move_down($lp_id, $categoryId = 0)
4156
    {
4157
        $courseId = api_get_course_int_id();
4158
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
4159
4160
        $categoryCondition = '';
4161
        if (!empty($categoryId)) {
4162
            $categoryId = (int) $categoryId;
4163
            $categoryCondition = " AND category_id = $categoryId";
4164
        }
4165
4166
        $sql = "SELECT * FROM $lp_table
4167
                WHERE c_id = $courseId
4168
                $categoryCondition
4169
                ORDER BY display_order";
4170
        $res = Database::query($sql);
4171
        if ($res === false) {
4172
            return false;
4173
        }
4174
        $lps = [];
4175
        $lp_order = [];
4176
        $num = Database::num_rows($res);
4177
        $max = 0;
4178
        // First check the order is correct, globally (might be wrong because
4179
        // of versions < 1.8.4).
4180
        if ($num > 0) {
4181
            $i = 1;
4182
            while ($row = Database::fetch_array($res)) {
4183
                $max = $i;
4184
                if ($row['display_order'] != $i) {
4185
                    // If we find a gap in the order, we need to fix it.
4186
                    $sql = "UPDATE $lp_table SET display_order = $i
4187
                              WHERE iid = ".$row['iid'];
4188
                    Database::query($sql);
4189
                }
4190
                $row['display_order'] = $i;
4191
                $lps[$row['iid']] = $row;
4192
                $lp_order[$i] = $row['iid'];
4193
                $i++;
4194
            }
4195
        }
4196
        if ($num > 1) { // If there's only one element, no need to sort.
4197
            $order = $lps[$lp_id]['display_order'];
4198
            if ($order < $max) { // If it's the first element, no need to move up.
4199
                $sql = "UPDATE $lp_table SET display_order = $order
4200
                        WHERE iid = ".$lp_order[$order + 1];
4201
                Database::query($sql);
4202
                $sql = "UPDATE $lp_table SET display_order = ".($order + 1)."
4203
                        WHERE iid = $lp_id";
4204
                Database::query($sql);
4205
            }
4206
        }
4207
4208
        return true;
4209
    }
4210
4211
    /**
4212
     * Updates learnpath attributes to point to the next element
4213
     * The last part is similar to set_current_item but processing the other way around.
4214
     */
4215
    public function next()
4216
    {
4217
        if ($this->debug > 0) {
4218
            error_log('In learnpath::next()', 0);
4219
        }
4220
        $this->last = $this->get_current_item_id();
4221
        $this->items[$this->last]->save(
4222
            false,
4223
            $this->prerequisites_match($this->last)
4224
        );
4225
        $this->autocomplete_parents($this->last);
4226
        $new_index = $this->get_next_index();
4227
        if ($this->debug > 2) {
4228
            error_log('New index: '.$new_index, 0);
4229
        }
4230
        $this->index = $new_index;
4231
        if ($this->debug > 2) {
4232
            error_log('Now having orderedlist['.$new_index.'] = '.$this->ordered_items[$new_index], 0);
4233
        }
4234
        $this->current = $this->ordered_items[$new_index];
4235
        if ($this->debug > 2) {
4236
            error_log('new item id is '.$this->current.'-'.$this->get_current_item_id(), 0);
4237
        }
4238
    }
4239
4240
    /**
4241
     * Open a resource = initialise all local variables relative to this resource. Depending on the child
4242
     * class, this might be redefined to allow several behaviours depending on the document type.
4243
     *
4244
     * @param int $id Resource ID
4245
     */
4246
    public function open($id)
4247
    {
4248
        // TODO:
4249
        // set the current resource attribute to this resource
4250
        // switch on element type (redefine in child class?)
4251
        // set status for this item to "opened"
4252
        // start timer
4253
        // initialise score
4254
        $this->index = 0; //or = the last item seen (see $this->last)
4255
    }
4256
4257
    /**
4258
     * Check that all prerequisites are fulfilled. Returns true and an
4259
     * empty string on success, returns false
4260
     * and the prerequisite string on error.
4261
     * This function is based on the rules for aicc_script language as
4262
     * described in the SCORM 1.2 CAM documentation page 108.
4263
     *
4264
     * @param int $itemId Optional item ID. If none given, uses the current open item.
4265
     *
4266
     * @return bool true if prerequisites are matched, false otherwise - Empty string if true returned, prerequisites
4267
     *              string otherwise
4268
     */
4269
    public function prerequisites_match($itemId = null)
4270
    {
4271
        $allow = api_get_configuration_value('allow_teachers_to_access_blocked_lp_by_prerequisite');
4272
        if ($allow) {
4273
            if (api_is_allowed_to_edit() ||
4274
                api_is_platform_admin(true) ||
4275
                api_is_drh() ||
4276
                api_is_coach(api_get_session_id(), api_get_course_int_id())
4277
            ) {
4278
                return true;
4279
            }
4280
        }
4281
4282
        $debug = $this->debug;
4283
        if ($debug > 0) {
4284
            error_log('In learnpath::prerequisites_match()');
4285
        }
4286
4287
        if (empty($itemId)) {
4288
            $itemId = $this->current;
4289
        }
4290
4291
        $currentItem = $this->getItem($itemId);
4292
4293
        if ($currentItem) {
4294
            if ($this->type == 2) {
4295
                // Getting prereq from scorm
4296
                $prereq_string = $this->get_scorm_prereq_string($itemId);
4297
            } else {
4298
                $prereq_string = $currentItem->get_prereq_string();
4299
            }
4300
4301
            if (empty($prereq_string)) {
4302
                if ($debug > 0) {
4303
                    error_log('Found prereq_string is empty return true');
4304
                }
4305
4306
                return true;
4307
            }
4308
4309
            // Clean spaces.
4310
            $prereq_string = str_replace(' ', '', $prereq_string);
4311
            if ($debug > 0) {
4312
                error_log('Found prereq_string: '.$prereq_string, 0);
4313
            }
4314
4315
            // Now send to the parse_prereq() function that will check this component's prerequisites.
4316
            $result = $currentItem->parse_prereq(
4317
                $prereq_string,
4318
                $this->items,
4319
                $this->refs_list,
4320
                $this->get_user_id()
4321
            );
4322
4323
            if ($result === false) {
4324
                $this->set_error_msg($currentItem->prereq_alert);
4325
            }
4326
        } else {
4327
            $result = true;
4328
            if ($debug > 1) {
4329
                error_log('$this->items['.$itemId.'] was not an object', 0);
4330
            }
4331
        }
4332
4333
        if ($debug > 1) {
4334
            error_log('End of prerequisites_match(). Error message is now '.$this->error, 0);
4335
        }
4336
4337
        return $result;
4338
    }
4339
4340
    /**
4341
     * Updates learnpath attributes to point to the previous element
4342
     * The last part is similar to set_current_item but processing the other way around.
4343
     */
4344
    public function previous()
4345
    {
4346
        $this->last = $this->get_current_item_id();
4347
        $this->items[$this->last]->save(
4348
            false,
4349
            $this->prerequisites_match($this->last)
4350
        );
4351
        $this->autocomplete_parents($this->last);
4352
        $new_index = $this->get_previous_index();
4353
        $this->index = $new_index;
4354
        $this->current = $this->ordered_items[$new_index];
4355
    }
4356
4357
    /**
4358
     * Publishes a learnpath. This basically means show or hide the learnpath
4359
     * to normal users.
4360
     * Can be used as abstract.
4361
     *
4362
     * @param int $lp_id          Learnpath ID
4363
     * @param int $set_visibility New visibility
4364
     *
4365
     * @return bool
4366
     */
4367
    public static function toggle_visibility($lp_id, $set_visibility = 1)
4368
    {
4369
        $action = 'visible';
4370
        if ($set_visibility != 1) {
4371
            $action = 'invisible';
4372
            self::toggle_publish($lp_id, 'i');
4373
        }
4374
4375
        return api_item_property_update(
4376
            api_get_course_info(),
4377
            TOOL_LEARNPATH,
4378
            $lp_id,
4379
            $action,
4380
            api_get_user_id()
4381
        );
4382
    }
4383
4384
    /**
4385
     * Publishes a learnpath category.
4386
     * This basically means show or hide the learnpath category to normal users.
4387
     *
4388
     * @param int $id
4389
     * @param int $visibility
4390
     *
4391
     * @throws \Doctrine\ORM\NonUniqueResultException
4392
     * @throws \Doctrine\ORM\ORMException
4393
     * @throws \Doctrine\ORM\OptimisticLockException
4394
     * @throws \Doctrine\ORM\TransactionRequiredException
4395
     *
4396
     * @return bool
4397
     */
4398
    public static function toggleCategoryVisibility($id, $visibility = 1)
4399
    {
4400
        $action = 'visible';
4401
        if ($visibility != 1) {
4402
            self::toggleCategoryPublish($id, 0);
4403
            $action = 'invisible';
4404
        }
4405
4406
        return api_item_property_update(
4407
            api_get_course_info(),
4408
            TOOL_LEARNPATH_CATEGORY,
4409
            $id,
4410
            $action,
4411
            api_get_user_id()
4412
        );
4413
    }
4414
4415
    /**
4416
     * Publishes a learnpath. This basically means show or hide the learnpath
4417
     * on the course homepage
4418
     * Can be used as abstract.
4419
     *
4420
     * @param int    $lp_id          Learnpath id
4421
     * @param string $set_visibility New visibility (v/i - visible/invisible)
4422
     *
4423
     * @return bool
4424
     */
4425
    public static function toggle_publish($lp_id, $set_visibility = 'v')
4426
    {
4427
        $course_id = api_get_course_int_id();
4428
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
4429
        $lp_id = (int) $lp_id;
4430
        $sql = "SELECT * FROM $tbl_lp
4431
                WHERE iid = $lp_id";
4432
        $result = Database::query($sql);
4433
        if (Database::num_rows($result)) {
4434
            $row = Database::fetch_array($result);
4435
            $name = Database::escape_string($row['name']);
4436
            if ($set_visibility == 'i') {
4437
                $v = 0;
4438
            }
4439
            if ($set_visibility == 'v') {
4440
                $v = 1;
4441
            }
4442
4443
            $session_id = api_get_session_id();
4444
            $session_condition = api_get_session_condition($session_id);
4445
4446
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
4447
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4448
            $oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
4449
4450
            $sql = "SELECT * FROM $tbl_tool
4451
                    WHERE
4452
                        c_id = $course_id AND
4453
                        (link = '$link' OR link = '$oldLink') AND
4454
                        image = 'scormbuilder.gif' AND
4455
                        (
4456
                            link LIKE '$link%' OR
4457
                            link LIKE '$oldLink%'
4458
                        )
4459
                        $session_condition
4460
                    ";
4461
4462
            $result = Database::query($sql);
4463
            $num = Database::num_rows($result);
4464
            if ($set_visibility == 'i' && $num > 0) {
4465
                $sql = "DELETE FROM $tbl_tool
4466
                        WHERE
4467
                            c_id = $course_id AND
4468
                            (link = '$link' OR link = '$oldLink') AND
4469
                            image='scormbuilder.gif'
4470
                            $session_condition";
4471
                Database::query($sql);
4472
            } elseif ($set_visibility == 'v' && $num == 0) {
4473
                $sql = "INSERT INTO $tbl_tool (category, c_id, name, link, image, visibility, admin, address, added_tool, session_id) VALUES
4474
                        ('authoring', $course_id, '$name', '$link', 'scormbuilder.gif', '$v', '0','pastillegris.gif', 0, $session_id)";
4475
                Database::query($sql);
4476
                $insertId = Database::insert_id();
4477
                if ($insertId) {
4478
                    $sql = "UPDATE $tbl_tool SET id = iid WHERE iid = $insertId";
4479
                    Database::query($sql);
4480
                }
4481
            } elseif ($set_visibility == 'v' && $num > 0) {
4482
                $sql = "UPDATE $tbl_tool SET
4483
                            c_id = $course_id,
4484
                            name = '$name',
4485
                            link = '$link',
4486
                            image = 'scormbuilder.gif',
4487
                            visibility = '$v',
4488
                            admin = '0',
4489
                            address = 'pastillegris.gif',
4490
                            added_tool = 0,
4491
                            session_id = $session_id
4492
                        WHERE
4493
                            c_id = ".$course_id." AND
4494
                            (link = '$link' OR link = '$oldLink') AND
4495
                            image='scormbuilder.gif'
4496
                            $session_condition
4497
                        ";
4498
                Database::query($sql);
4499
            } else {
4500
                // Parameter and database incompatible, do nothing, exit.
4501
                return false;
4502
            }
4503
        } else {
4504
            return false;
4505
        }
4506
    }
4507
4508
    /**
4509
     * Publishes a learnpath.
4510
     * Show or hide the learnpath category on the course homepage.
4511
     *
4512
     * @param int $id
4513
     * @param int $setVisibility
4514
     *
4515
     * @throws \Doctrine\ORM\NonUniqueResultException
4516
     * @throws \Doctrine\ORM\ORMException
4517
     * @throws \Doctrine\ORM\OptimisticLockException
4518
     * @throws \Doctrine\ORM\TransactionRequiredException
4519
     *
4520
     * @return bool
4521
     */
4522
    public static function toggleCategoryPublish($id, $setVisibility = 1)
4523
    {
4524
        $courseId = api_get_course_int_id();
4525
        $sessionId = api_get_session_id();
4526
        $sessionCondition = api_get_session_condition(
4527
            $sessionId,
4528
            true,
4529
            false,
4530
            't.sessionId'
4531
        );
4532
4533
        $em = Database::getManager();
4534
        $category = self::getCategory($id);
4535
4536
        if (!$category) {
4537
            return false;
4538
        }
4539
4540
        if (empty($courseId)) {
4541
            return false;
4542
        }
4543
4544
        $link = self::getCategoryLinkForTool($id);
4545
4546
        /** @var CTool $tool */
4547
        $tool = $em->createQuery("
4548
                SELECT t FROM ChamiloCourseBundle:CTool t
4549
                WHERE
4550
                    t.cId = :course AND
4551
                    t.link = :link1 AND
4552
                    t.image = 'lp_category.gif' AND
4553
                    t.link LIKE :link2
4554
                    $sessionCondition
4555
            ")
4556
            ->setParameters([
4557
                'course' => $courseId,
4558
                'link1' => $link,
4559
                'link2' => "$link%",
4560
            ])
4561
            ->getOneOrNullResult();
4562
4563
        if ($setVisibility == 0 && $tool) {
4564
            $em->remove($tool);
4565
            $em->flush();
4566
4567
            return true;
4568
        }
4569
4570
        if ($setVisibility == 1 && !$tool) {
4571
            $tool = new CTool();
4572
            $tool
4573
                ->setCategory('authoring')
4574
                ->setCId($courseId)
4575
                ->setName(strip_tags($category->getName()))
4576
                ->setLink($link)
4577
                ->setImage('lp_category.gif')
4578
                ->setVisibility(1)
4579
                ->setAdmin(0)
4580
                ->setAddress('pastillegris.gif')
4581
                ->setAddedTool(0)
4582
                ->setSessionId($sessionId)
4583
                ->setTarget('_self');
4584
4585
            $em->persist($tool);
4586
            $em->flush();
4587
4588
            $tool->setId($tool->getIid());
4589
4590
            $em->persist($tool);
4591
            $em->flush();
4592
4593
            return true;
4594
        }
4595
4596
        if ($setVisibility == 1 && $tool) {
4597
            $tool
4598
                ->setName(strip_tags($category->getName()))
4599
                ->setVisibility(1);
4600
4601
            $em->persist($tool);
4602
            $em->flush();
4603
4604
            return true;
4605
        }
4606
4607
        return false;
4608
    }
4609
4610
    /**
4611
     * Check if the learnpath category is visible for a user.
4612
     *
4613
     * @param int
4614
     * @param int
4615
     *
4616
     * @return bool
4617
     */
4618
    public static function categoryIsVisibleForStudent(
4619
        CLpCategory $category,
4620
        User $user,
4621
        $courseId = 0,
4622
        $sessionId = 0
4623
    ) {
4624
        if (empty($category)) {
4625
            return false;
4626
        }
4627
4628
        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
4629
4630
        if ($isAllowedToEdit) {
4631
            return true;
4632
        }
4633
4634
        $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
4635
        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
4636
4637
        $courseInfo = api_get_course_info_by_id($courseId);
4638
4639
        $categoryVisibility = api_get_item_visibility(
4640
            $courseInfo,
4641
            TOOL_LEARNPATH_CATEGORY,
4642
            $category->getId(),
4643
            $sessionId
4644
        );
4645
4646
        if ($categoryVisibility !== 1 && $categoryVisibility != -1) {
4647
            return false;
4648
        }
4649
4650
        $subscriptionSettings = self::getSubscriptionSettings();
4651
4652
        if ($subscriptionSettings['allow_add_users_to_lp_category'] == false) {
4653
            return true;
4654
        }
4655
4656
        $noUserSubscribed = false;
4657
        $noGroupSubscribed = true;
4658
        $users = $category->getUsers();
4659
        if (empty($users) || !$users->count()) {
4660
            $noUserSubscribed = true;
4661
        } elseif ($category->hasUserAdded($user)) {
4662
            return true;
4663
        }
4664
4665
        $groups = GroupManager::getAllGroupPerUserSubscription($user->getId());
4666
        $em = Database::getManager();
4667
4668
        /** @var ItemPropertyRepository $itemRepo */
4669
        $itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
4670
4671
        /** @var CourseRepository $courseRepo */
4672
        $courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
4673
        $session = null;
4674
        if (!empty($sessionId)) {
4675
            $session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
4676
        }
4677
4678
        $course = $courseRepo->find($courseId);
4679
4680
        if ($courseId != 0) {
4681
            // Subscribed groups to a LP
4682
            $subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
4683
                TOOL_LEARNPATH_CATEGORY,
4684
                $category->getId(),
4685
                $course,
4686
                $session
4687
            );
4688
        }
4689
4690
        if (!empty($subscribedGroupsInLp)) {
4691
            $noGroupSubscribed = false;
4692
            if (!empty($groups)) {
4693
                $groups = array_column($groups, 'iid');
4694
                /** @var CItemProperty $item */
4695
                foreach ($subscribedGroupsInLp as $item) {
4696
                    if ($item->getGroup() &&
4697
                        in_array($item->getGroup()->getId(), $groups)
4698
                    ) {
4699
                        return true;
4700
                    }
4701
                }
4702
            }
4703
        }
4704
        $response = $noGroupSubscribed && $noUserSubscribed;
4705
4706
        return $response;
4707
    }
4708
4709
    /**
4710
     * Check if a learnpath category is published as course tool.
4711
     *
4712
     * @param int $courseId
4713
     *
4714
     * @return bool
4715
     */
4716
    public static function categoryIsPublished(CLpCategory $category, $courseId)
4717
    {
4718
        $link = self::getCategoryLinkForTool($category->getId());
4719
        $em = Database::getManager();
4720
4721
        $tools = $em
4722
            ->createQuery("
4723
                SELECT t FROM ChamiloCourseBundle:CTool t
4724
                WHERE t.cId = :course AND
4725
                    t.name = :name AND
4726
                    t.image = 'lp_category.gif' AND
4727
                    t.link LIKE :link
4728
            ")
4729
            ->setParameters([
4730
                'course' => $courseId,
4731
                'name' => strip_tags($category->getName()),
4732
                'link' => "$link%",
4733
            ])
4734
            ->getResult();
4735
4736
        /** @var CTool $tool */
4737
        $tool = current($tools);
4738
4739
        return $tool ? $tool->getVisibility() : false;
4740
    }
4741
4742
    /**
4743
     * Restart the whole learnpath. Return the URL of the first element.
4744
     * Make sure the results are saved with anoter method. This method should probably be redefined in children classes.
4745
     * To use a similar method  statically, use the create_new_attempt() method.
4746
     *
4747
     * @return bool
4748
     */
4749
    public function restart()
4750
    {
4751
        if ($this->debug > 0) {
4752
            error_log('In learnpath::restart()', 0);
4753
        }
4754
        // TODO
4755
        // Call autosave method to save the current progress.
4756
        //$this->index = 0;
4757
        if (api_is_invitee()) {
4758
            return false;
4759
        }
4760
        $session_id = api_get_session_id();
4761
        $course_id = api_get_course_int_id();
4762
        $lp_view_table = Database::get_course_table(TABLE_LP_VIEW);
4763
        $sql = "INSERT INTO $lp_view_table (c_id, lp_id, user_id, view_count, session_id)
4764
                VALUES ($course_id, ".$this->lp_id.",".$this->get_user_id().",".($this->attempt + 1).", $session_id)";
4765
        if ($this->debug > 2) {
4766
            error_log('Inserting new lp_view for restart: '.$sql, 0);
4767
        }
4768
        Database::query($sql);
4769
        $view_id = Database::insert_id();
4770
4771
        if ($view_id) {
4772
            $sql = "UPDATE $lp_view_table SET id = iid WHERE iid = $view_id";
4773
            Database::query($sql);
4774
            $this->lp_view_id = $view_id;
4775
            $this->attempt = $this->attempt + 1;
4776
        } else {
4777
            $this->error = 'Could not insert into item_view table...';
4778
4779
            return false;
4780
        }
4781
        $this->autocomplete_parents($this->current);
4782
        foreach ($this->items as $index => $dummy) {
4783
            $this->items[$index]->restart();
4784
            $this->items[$index]->set_lp_view($this->lp_view_id);
4785
        }
4786
        $this->first();
4787
4788
        return true;
4789
    }
4790
4791
    /**
4792
     * Saves the current item.
4793
     *
4794
     * @return bool
4795
     */
4796
    public function save_current()
4797
    {
4798
        $debug = $this->debug;
4799
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4800
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4801
        if ($debug) {
4802
            error_log('save_current() saving item '.$this->current, 0);
4803
            error_log(''.print_r($this->items, true), 0);
4804
        }
4805
        if (isset($this->items[$this->current]) &&
4806
            is_object($this->items[$this->current])
4807
        ) {
4808
            if ($debug) {
4809
                error_log('Before save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4810
            }
4811
4812
            $res = $this->items[$this->current]->save(
4813
                false,
4814
                $this->prerequisites_match($this->current)
4815
            );
4816
            $this->autocomplete_parents($this->current);
4817
            $status = $this->items[$this->current]->get_status();
4818
            $this->update_queue[$this->current] = $status;
4819
4820
            if ($debug) {
4821
                error_log('After save last_scorm_session_time: '.$this->items[$this->current]->getLastScormSessionTime());
4822
            }
4823
4824
            return $res;
4825
        }
4826
4827
        return false;
4828
    }
4829
4830
    /**
4831
     * Saves the given item.
4832
     *
4833
     * @param int  $item_id      Optional (will take from $_REQUEST if null)
4834
     * @param bool $from_outside Save from url params (true) or from current attributes (false). Default true
4835
     *
4836
     * @return bool
4837
     */
4838
    public function save_item($item_id = null, $from_outside = true)
4839
    {
4840
        $debug = $this->debug;
4841
        if ($debug) {
4842
            error_log('In learnpath::save_item('.$item_id.','.intval($from_outside).')', 0);
4843
        }
4844
        // TODO: Do a better check on the index pointing to the right item (it is supposed to be working
4845
        // on $ordered_items[] but not sure it's always safe to use with $items[]).
4846
        if (empty($item_id)) {
4847
            $item_id = (int) $_REQUEST['id'];
4848
        }
4849
4850
        if (empty($item_id)) {
4851
            $item_id = $this->get_current_item_id();
4852
        }
4853
        if (isset($this->items[$item_id]) &&
4854
            is_object($this->items[$item_id])
4855
        ) {
4856
            if ($debug) {
4857
                error_log('Object exists');
4858
            }
4859
4860
            // Saving the item.
4861
            $res = $this->items[$item_id]->save(
4862
                $from_outside,
4863
                $this->prerequisites_match($item_id)
4864
            );
4865
4866
            if ($debug) {
4867
                error_log('update_queue before:');
4868
                error_log(print_r($this->update_queue, 1));
4869
            }
4870
            $this->autocomplete_parents($item_id);
4871
4872
            $status = $this->items[$item_id]->get_status();
4873
            $this->update_queue[$item_id] = $status;
4874
4875
            if ($debug) {
4876
                error_log('get_status(): '.$status);
4877
                error_log('update_queue after:');
4878
                error_log(print_r($this->update_queue, 1));
4879
            }
4880
4881
            return $res;
4882
        }
4883
4884
        return false;
4885
    }
4886
4887
    /**
4888
     * Saves the last item seen's ID only in case.
4889
     */
4890
    public function save_last($score = null)
4891
    {
4892
        $course_id = api_get_course_int_id();
4893
        $debug = $this->debug;
4894
        if ($debug) {
4895
            error_log('In learnpath::save_last()', 0);
4896
        }
4897
        $session_condition = api_get_session_condition(
4898
            api_get_session_id(),
4899
            true,
4900
            false
4901
        );
4902
        $table = Database::get_course_table(TABLE_LP_VIEW);
4903
4904
        $userId = $this->get_user_id();
4905
        if (empty($userId)) {
4906
            $userId = api_get_user_id();
4907
            if ($debug) {
4908
                error_log('$this->get_user_id() was empty, used api_get_user_id() instead in '.__FILE__.' line '.__LINE__);
4909
            }
4910
        }
4911
        if (isset($this->current) && !api_is_invitee()) {
4912
            if ($debug) {
4913
                error_log('Saving current item ('.$this->current.') for later review', 0);
4914
            }
4915
            $sql = "UPDATE $table SET
4916
                        last_item = ".$this->get_current_item_id()."
4917
                    WHERE
4918
                        c_id = $course_id AND
4919
                        lp_id = ".$this->get_id()." AND
4920
                        user_id = ".$userId." ".$session_condition;
4921
4922
            if ($debug) {
4923
                error_log('Saving last item seen : '.$sql, 0);
4924
            }
4925
            Database::query($sql);
4926
        }
4927
4928
        if (!api_is_invitee()) {
4929
            // Save progress.
4930
            list($progress) = $this->get_progress_bar_text('%');
4931
            $scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
4932
            if ($scoreAsProgressSetting && (null === $score || empty($score) || -1 == $score)) {
4933
                return false;
4934
            }
4935
4936
            if ($progress >= 0 && $progress <= 100) {
4937
                $progress = (int) $progress;
4938
                $sql = "UPDATE $table SET
4939
                            progress = $progress
4940
                        WHERE
4941
                            c_id = $course_id AND
4942
                            lp_id = ".$this->get_id()." AND
4943
                            user_id = ".$userId." ".$session_condition;
4944
                // Ignore errors as some tables might not have the progress field just yet.
4945
                Database::query($sql);
4946
                $this->progress_db = $progress;
4947
            }
4948
        }
4949
    }
4950
4951
    /**
4952
     * Sets the current item ID (checks if valid and authorized first).
4953
     *
4954
     * @param int $item_id New item ID. If not given or not authorized, defaults to current
4955
     */
4956
    public function set_current_item($item_id = null)
4957
    {
4958
        $debug = $this->debug;
4959
        if ($debug) {
4960
            error_log('In learnpath::set_current_item('.$item_id.')', 0);
4961
        }
4962
        if (empty($item_id)) {
4963
            if ($debug) {
4964
                error_log('No new current item given, ignore...', 0);
4965
            }
4966
            // Do nothing.
4967
        } else {
4968
            if ($debug) {
4969
                error_log('New current item given is '.$item_id.'...', 0);
4970
            }
4971
            if (is_numeric($item_id)) {
4972
                $item_id = (int) $item_id;
4973
                // TODO: Check in database here.
4974
                $this->last = $this->current;
4975
                $this->current = $item_id;
4976
                // TODO: Update $this->index as well.
4977
                foreach ($this->ordered_items as $index => $item) {
4978
                    if ($item == $this->current) {
4979
                        $this->index = $index;
4980
                        break;
4981
                    }
4982
                }
4983
                if ($debug) {
4984
                    error_log('set_current_item('.$item_id.') done. Index is now : '.$this->index);
4985
                }
4986
            } else {
4987
                if ($debug) {
4988
                    error_log('set_current_item('.$item_id.') failed. Not a numeric value: ');
4989
                }
4990
            }
4991
        }
4992
    }
4993
4994
    /**
4995
     * Sets the encoding.
4996
     *
4997
     * @param string $enc New encoding
4998
     *
4999
     * @return bool
5000
     *
5001
     * @todo (as of Chamilo 1.8.8): Check in the future whether this method is needed.
5002
     */
5003
    public function set_encoding($enc = 'UTF-8')
5004
    {
5005
        $enc = api_refine_encoding_id($enc);
5006
        if (empty($enc)) {
5007
            $enc = api_get_system_encoding();
5008
        }
5009
        if (api_is_encoding_supported($enc)) {
5010
            $lp = $this->get_id();
5011
            if ($lp != 0) {
5012
                $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5013
                $sql = "UPDATE $tbl_lp SET default_encoding = '$enc'
5014
                        WHERE iid = ".$lp;
5015
                $res = Database::query($sql);
5016
5017
                return $res;
5018
            }
5019
        }
5020
5021
        return false;
5022
    }
5023
5024
    /**
5025
     * Sets the JS lib setting in the database directly.
5026
     * This is the JavaScript library file this lp needs to load on startup.
5027
     *
5028
     * @param string $lib Proximity setting
5029
     *
5030
     * @return bool True on update success. False otherwise.
5031
     */
5032
    public function set_jslib($lib = '')
5033
    {
5034
        $lp = $this->get_id();
5035
5036
        if ($lp != 0) {
5037
            $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
5038
            $lib = Database::escape_string($lib);
5039
            $sql = "UPDATE $tbl_lp SET js_lib = '$lib'
5040
                    WHERE iid = $lp";
5041
            $res = Database::query($sql);
5042
5043
            return $res;
5044
        }
5045
5046
        return false;
5047
    }
5048
5049
    /**
5050
     * Sets the name of the LP maker (publisher) (and save).
5051
     *
5052
     * @param string $name Optional string giving the new content_maker of this learnpath
5053
     *
5054
     * @return bool True
5055
     */
5056
    public function set_maker($name = '')
5057
    {
5058
        if (empty($name)) {
5059
            return false;
5060
        }
5061
        $this->maker = $name;
5062
        $table = Database::get_course_table(TABLE_LP_MAIN);
5063
        $lp_id = $this->get_id();
5064
        $sql = "UPDATE $table SET
5065
                content_maker = '".Database::escape_string($this->maker)."'
5066
                WHERE iid = $lp_id";
5067
        Database::query($sql);
5068
5069
        return true;
5070
    }
5071
5072
    /**
5073
     * Sets the name of the current learnpath (and save).
5074
     *
5075
     * @param string $name Optional string giving the new name of this learnpath
5076
     *
5077
     * @return bool True/False
5078
     */
5079
    public function set_name($name = null)
5080
    {
5081
        if (empty($name)) {
5082
            return false;
5083
        }
5084
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5085
        $name = Database::escape_string($name);
5086
5087
        $this->name = $name;
5088
5089
        $lp_id = $this->get_id();
5090
        $course_id = $this->course_info['real_id'];
5091
        $sql = "UPDATE $lp_table SET
5092
                name = '$name'
5093
                WHERE iid = $lp_id";
5094
        $result = Database::query($sql);
5095
        // If the lp is visible on the homepage, change his name there.
5096
        if (Database::affected_rows($result)) {
5097
            $session_id = api_get_session_id();
5098
            $session_condition = api_get_session_condition($session_id);
5099
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
5100
            $link = 'lp/lp_controller.php?action=view&lp_id='.$lp_id.'&id_session='.$session_id;
5101
            $sql = "UPDATE $tbl_tool SET name = '$name'
5102
            	    WHERE
5103
            	        c_id = $course_id AND
5104
            	        (link='$link' AND image='scormbuilder.gif' $session_condition)";
5105
            Database::query($sql);
5106
5107
            return true;
5108
        }
5109
5110
        return false;
5111
    }
5112
5113
    /**
5114
     * Set index specified prefix terms for all items in this path.
5115
     *
5116
     * @param string $terms_string Comma-separated list of terms
5117
     * @param string $prefix       Xapian term prefix
5118
     *
5119
     * @return bool False on error, true otherwise
5120
     */
5121
    public function set_terms_by_prefix($terms_string, $prefix)
5122
    {
5123
        $course_id = api_get_course_int_id();
5124
        if (api_get_setting('search_enabled') !== 'true') {
5125
            return false;
5126
        }
5127
5128
        if (!extension_loaded('xapian')) {
5129
            return false;
5130
        }
5131
5132
        $terms_string = trim($terms_string);
5133
        $terms = explode(',', $terms_string);
5134
        array_walk($terms, 'trim_value');
5135
        $stored_terms = $this->get_common_index_terms_by_prefix($prefix);
5136
5137
        // Don't do anything if no change, verify only at DB, not the search engine.
5138
        if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) {
5139
            return false;
5140
        }
5141
5142
        require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API.
5143
        require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php';
5144
5145
        $items_table = Database::get_course_table(TABLE_LP_ITEM);
5146
        // TODO: Make query secure agains XSS : use member attr instead of post var.
5147
        $lp_id = (int) $_POST['lp_id'];
5148
        $sql = "SELECT * FROM $items_table WHERE c_id = $course_id AND lp_id = $lp_id";
5149
        $result = Database::query($sql);
5150
        $di = new ChamiloIndexer();
5151
5152
        while ($lp_item = Database::fetch_array($result)) {
5153
            // Get search_did.
5154
            $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
5155
            $sql = 'SELECT * FROM %s
5156
                    WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d
5157
                    LIMIT 1';
5158
            $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']);
5159
5160
            //echo $sql; echo '<br>';
5161
            $res = Database::query($sql);
5162
            if (Database::num_rows($res) > 0) {
5163
                $se_ref = Database::fetch_array($res);
5164
                // Compare terms.
5165
                $doc = $di->get_document($se_ref['search_did']);
5166
                $xapian_terms = xapian_get_doc_terms($doc, $prefix);
5167
                $xterms = [];
5168
                foreach ($xapian_terms as $xapian_term) {
5169
                    $xterms[] = substr($xapian_term['name'], 1);
5170
                }
5171
5172
                $dterms = $terms;
5173
                $missing_terms = array_diff($dterms, $xterms);
5174
                $deprecated_terms = array_diff($xterms, $dterms);
5175
5176
                // Save it to search engine.
5177
                foreach ($missing_terms as $term) {
5178
                    $doc->add_term($prefix.$term, 1);
5179
                }
5180
                foreach ($deprecated_terms as $term) {
5181
                    $doc->remove_term($prefix.$term);
5182
                }
5183
                $di->getDb()->replace_document((int) $se_ref['search_did'], $doc);
5184
                $di->getDb()->flush();
5185
            }
5186
        }
5187
5188
        return true;
5189
    }
5190
5191
    /**
5192
     * Sets the theme of the LP (local/remote) (and save).
5193
     *
5194
     * @param string $name Optional string giving the new theme of this learnpath
5195
     *
5196
     * @return bool Returns true if theme name is not empty
5197
     */
5198
    public function set_theme($name = '')
5199
    {
5200
        $this->theme = $name;
5201
        $table = Database::get_course_table(TABLE_LP_MAIN);
5202
        $lp_id = $this->get_id();
5203
        $sql = "UPDATE $table
5204
                SET theme = '".Database::escape_string($this->theme)."'
5205
                WHERE iid = $lp_id";
5206
        Database::query($sql);
5207
5208
        return true;
5209
    }
5210
5211
    /**
5212
     * Sets the image of an LP (and save).
5213
     *
5214
     * @param string $name Optional string giving the new image of this learnpath
5215
     *
5216
     * @return bool Returns true if theme name is not empty
5217
     */
5218
    public function set_preview_image($name = '')
5219
    {
5220
        $this->preview_image = $name;
5221
        $table = Database::get_course_table(TABLE_LP_MAIN);
5222
        $lp_id = $this->get_id();
5223
        $sql = "UPDATE $table SET
5224
                preview_image = '".Database::escape_string($this->preview_image)."'
5225
                WHERE iid = $lp_id";
5226
        Database::query($sql);
5227
5228
        return true;
5229
    }
5230
5231
    /**
5232
     * Sets the author of a LP (and save).
5233
     *
5234
     * @param string $name Optional string giving the new author of this learnpath
5235
     *
5236
     * @return bool Returns true if author's name is not empty
5237
     */
5238
    public function set_author($name = '')
5239
    {
5240
        $this->author = $name;
5241
        $table = Database::get_course_table(TABLE_LP_MAIN);
5242
        $lp_id = $this->get_id();
5243
        $sql = "UPDATE $table SET author = '".Database::escape_string($name)."'
5244
                WHERE iid = $lp_id";
5245
        Database::query($sql);
5246
5247
        return true;
5248
    }
5249
5250
    /**
5251
     * Sets the hide_toc_frame parameter of a LP (and save).
5252
     *
5253
     * @param int $hide 1 if frame is hidden 0 then else
5254
     *
5255
     * @return bool Returns true if author's name is not empty
5256
     */
5257
    public function set_hide_toc_frame($hide)
5258
    {
5259
        if (intval($hide) == $hide) {
5260
            $this->hide_toc_frame = $hide;
5261
            $table = Database::get_course_table(TABLE_LP_MAIN);
5262
            $lp_id = $this->get_id();
5263
            $sql = "UPDATE $table SET
5264
                    hide_toc_frame = '".(int) $this->hide_toc_frame."'
5265
                    WHERE iid = $lp_id";
5266
            Database::query($sql);
5267
5268
            return true;
5269
        }
5270
5271
        return false;
5272
    }
5273
5274
    /**
5275
     * Sets the prerequisite of a LP (and save).
5276
     *
5277
     * @param int $prerequisite integer giving the new prerequisite of this learnpath
5278
     *
5279
     * @return bool returns true if prerequisite is not empty
5280
     */
5281
    public function set_prerequisite($prerequisite)
5282
    {
5283
        $this->prerequisite = (int) $prerequisite;
5284
        $table = Database::get_course_table(TABLE_LP_MAIN);
5285
        $lp_id = $this->get_id();
5286
        $sql = "UPDATE $table SET prerequisite = '".$this->prerequisite."'
5287
                WHERE iid = $lp_id";
5288
        Database::query($sql);
5289
5290
        return true;
5291
    }
5292
5293
    /**
5294
     * Sets the location/proximity of the LP (local/remote) (and save).
5295
     *
5296
     * @param string $name Optional string giving the new location of this learnpath
5297
     *
5298
     * @return bool True on success / False on error
5299
     */
5300
    public function set_proximity($name = '')
5301
    {
5302
        if (empty($name)) {
5303
            return false;
5304
        }
5305
5306
        $this->proximity = $name;
5307
        $table = Database::get_course_table(TABLE_LP_MAIN);
5308
        $lp_id = $this->get_id();
5309
        $sql = "UPDATE $table SET
5310
                    content_local = '".Database::escape_string($name)."'
5311
                WHERE iid = $lp_id";
5312
        Database::query($sql);
5313
5314
        return true;
5315
    }
5316
5317
    /**
5318
     * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item.
5319
     *
5320
     * @param int $id DB ID of the item
5321
     */
5322
    public function set_previous_item($id)
5323
    {
5324
        if ($this->debug > 0) {
5325
            error_log('In learnpath::set_previous_item()', 0);
5326
        }
5327
        $this->last = $id;
5328
    }
5329
5330
    /**
5331
     * Sets use_max_score.
5332
     *
5333
     * @param int $use_max_score Optional string giving the new location of this learnpath
5334
     *
5335
     * @return bool True on success / False on error
5336
     */
5337
    public function set_use_max_score($use_max_score = 1)
5338
    {
5339
        $use_max_score = (int) $use_max_score;
5340
        $this->use_max_score = $use_max_score;
5341
        $table = Database::get_course_table(TABLE_LP_MAIN);
5342
        $lp_id = $this->get_id();
5343
        $sql = "UPDATE $table SET
5344
                    use_max_score = '".$this->use_max_score."'
5345
                WHERE iid = $lp_id";
5346
        Database::query($sql);
5347
5348
        return true;
5349
    }
5350
5351
    /**
5352
     * Sets and saves the expired_on date.
5353
     *
5354
     * @param string $expired_on Optional string giving the new author of this learnpath
5355
     *
5356
     * @throws \Doctrine\ORM\OptimisticLockException
5357
     *
5358
     * @return bool Returns true if author's name is not empty
5359
     */
5360
    public function set_expired_on($expired_on)
5361
    {
5362
        $em = Database::getManager();
5363
        /** @var CLp $lp */
5364
        $lp = $em
5365
            ->getRepository('ChamiloCourseBundle:CLp')
5366
            ->findOneBy(
5367
                [
5368
                    'iid' => $this->get_id(),
5369
                ]
5370
            );
5371
5372
        if (!$lp) {
5373
            return false;
5374
        }
5375
5376
        $this->expired_on = !empty($expired_on) ? api_get_utc_datetime($expired_on, false, true) : null;
5377
5378
        $lp->setExpiredOn($this->expired_on);
5379
        $em->persist($lp);
5380
        $em->flush();
5381
5382
        return true;
5383
    }
5384
5385
    /**
5386
     * Sets and saves the publicated_on date.
5387
     *
5388
     * @param string $publicated_on Optional string giving the new author of this learnpath
5389
     *
5390
     * @throws \Doctrine\ORM\OptimisticLockException
5391
     *
5392
     * @return bool Returns true if author's name is not empty
5393
     */
5394
    public function set_publicated_on($publicated_on)
5395
    {
5396
        $em = Database::getManager();
5397
        /** @var CLp $lp */
5398
        $lp = $em
5399
            ->getRepository('ChamiloCourseBundle:CLp')
5400
            ->findOneBy(
5401
                [
5402
                    'iid' => $this->get_id(),
5403
                ]
5404
            );
5405
5406
        if (!$lp) {
5407
            return false;
5408
        }
5409
5410
        $this->publicated_on = !empty($publicated_on) ? api_get_utc_datetime($publicated_on, false, true) : null;
5411
        $lp->setPublicatedOn($this->publicated_on);
5412
        $em->persist($lp);
5413
        $em->flush();
5414
5415
        return true;
5416
    }
5417
5418
    /**
5419
     * Sets and saves the expired_on date.
5420
     *
5421
     * @return bool Returns true if author's name is not empty
5422
     */
5423
    public function set_modified_on()
5424
    {
5425
        $this->modified_on = api_get_utc_datetime();
5426
        $table = Database::get_course_table(TABLE_LP_MAIN);
5427
        $lp_id = $this->get_id();
5428
        $sql = "UPDATE $table SET modified_on = '".$this->modified_on."'
5429
                WHERE iid = $lp_id";
5430
        Database::query($sql);
5431
5432
        return true;
5433
    }
5434
5435
    /**
5436
     * Sets the object's error message.
5437
     *
5438
     * @param string $error Error message. If empty, reinits the error string
5439
     */
5440
    public function set_error_msg($error = '')
5441
    {
5442
        if ($this->debug > 0) {
5443
            error_log('In learnpath::set_error_msg()', 0);
5444
        }
5445
        if (empty($error)) {
5446
            $this->error = '';
5447
        } else {
5448
            $this->error .= $error;
5449
        }
5450
    }
5451
5452
    /**
5453
     * Launches the current item if not 'sco'
5454
     * (starts timer and make sure there is a record ready in the DB).
5455
     *
5456
     * @param bool $allow_new_attempt Whether to allow a new attempt or not
5457
     *
5458
     * @return bool
5459
     */
5460
    public function start_current_item($allow_new_attempt = false)
5461
    {
5462
        $debug = $this->debug;
5463
        if ($debug) {
5464
            error_log('In learnpath::start_current_item()');
5465
            error_log('current: '.$this->current);
5466
        }
5467
        if ($this->current != 0 && isset($this->items[$this->current]) && is_object($this->items[$this->current])) {
5468
            $type = $this->get_type();
5469
            $item_type = $this->items[$this->current]->get_type();
5470
            if (($type == 2 && $item_type != 'sco') ||
5471
                ($type == 3 && $item_type != 'au') ||
5472
                (
5473
                    $type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES &&
5474
                    WhispeakAuthPlugin::isAllowedToSaveLpItem($this->current)
5475
                )
5476
            ) {
5477
                if ($debug) {
5478
                    error_log('item type: '.$item_type);
5479
                    error_log('lp type: '.$type);
5480
                }
5481
                $this->items[$this->current]->open($allow_new_attempt);
5482
                $this->autocomplete_parents($this->current);
5483
                $prereq_check = $this->prerequisites_match($this->current);
5484
                if ($debug) {
5485
                    error_log('start_current_item will save item with prereq: '.$prereq_check);
5486
                }
5487
                $this->items[$this->current]->save(false, $prereq_check);
5488
            }
5489
            // If sco, then it is supposed to have been updated by some other call.
5490
            if ($item_type == 'sco') {
5491
                $this->items[$this->current]->restart();
5492
            }
5493
        }
5494
        if ($debug) {
5495
            error_log('lp_view_session_id');
5496
            error_log($this->lp_view_session_id);
5497
            error_log('api session id');
5498
            error_log(api_get_session_id());
5499
            error_log('End of learnpath::start_current_item()');
5500
        }
5501
5502
        return true;
5503
    }
5504
5505
    /**
5506
     * Stops the processing and counters for the old item (as held in $this->last).
5507
     *
5508
     * @return bool True/False
5509
     */
5510
    public function stop_previous_item()
5511
    {
5512
        $debug = $this->debug;
5513
        if ($debug) {
5514
            error_log('In learnpath::stop_previous_item()', 0);
5515
        }
5516
5517
        if ($this->last != 0 && $this->last != $this->current &&
5518
            isset($this->items[$this->last]) && is_object($this->items[$this->last])
5519
        ) {
5520
            if ($debug) {
5521
                error_log('In learnpath::stop_previous_item() - '.$this->last.' is object');
5522
            }
5523
            switch ($this->get_type()) {
5524
                case '3':
5525
                    if ($this->items[$this->last]->get_type() != 'au') {
5526
                        if ($debug) {
5527
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 3 is <> au');
5528
                        }
5529
                        $this->items[$this->last]->close();
5530
                    } else {
5531
                        if ($debug) {
5532
                            error_log('In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals');
5533
                        }
5534
                    }
5535
                    break;
5536
                case '2':
5537
                    if ($this->items[$this->last]->get_type() != 'sco') {
5538
                        if ($debug) {
5539
                            error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 2 is <> sco');
5540
                        }
5541
                        $this->items[$this->last]->close();
5542
                    } else {
5543
                        if ($debug) {
5544
                            error_log('In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals');
5545
                        }
5546
                    }
5547
                    break;
5548
                case '1':
5549
                default:
5550
                    if ($debug) {
5551
                        error_log('In learnpath::stop_previous_item() - '.$this->last.' in lp_type 1 is asset');
5552
                    }
5553
                    $this->items[$this->last]->close();
5554
                    break;
5555
            }
5556
        } else {
5557
            if ($debug) {
5558
                error_log('In learnpath::stop_previous_item() - No previous element found, ignoring...');
5559
            }
5560
5561
            return false;
5562
        }
5563
5564
        return true;
5565
    }
5566
5567
    /**
5568
     * Updates the default view mode from fullscreen to embedded and inversely.
5569
     *
5570
     * @return string The current default view mode ('fullscreen' or 'embedded')
5571
     */
5572
    public function update_default_view_mode()
5573
    {
5574
        $table = Database::get_course_table(TABLE_LP_MAIN);
5575
        $sql = "SELECT * FROM $table
5576
                WHERE iid = ".$this->get_id();
5577
        $res = Database::query($sql);
5578
        if (Database::num_rows($res) > 0) {
5579
            $row = Database::fetch_array($res);
5580
            $default_view_mode = $row['default_view_mod'];
5581
            $view_mode = $default_view_mode;
5582
            switch ($default_view_mode) {
5583
                case 'fullscreen': // default with popup
5584
                    $view_mode = 'embedded';
5585
                    break;
5586
                case 'embedded': // default view with left menu
5587
                    $view_mode = 'embedframe';
5588
                    break;
5589
                case 'embedframe': //folded menu
5590
                    $view_mode = 'impress';
5591
                    break;
5592
                case 'impress':
5593
                    $view_mode = 'fullscreen';
5594
                    break;
5595
            }
5596
            $sql = "UPDATE $table SET default_view_mod = '$view_mode'
5597
                    WHERE iid = ".$this->get_id();
5598
            Database::query($sql);
5599
            $this->mode = $view_mode;
5600
5601
            return $view_mode;
5602
        }
5603
5604
        return -1;
5605
    }
5606
5607
    /**
5608
     * Updates the default behaviour about auto-commiting SCORM updates.
5609
     *
5610
     * @return bool True if auto-commit has been set to 'on', false otherwise
5611
     */
5612
    public function update_default_scorm_commit()
5613
    {
5614
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5615
        $sql = "SELECT * FROM $lp_table
5616
                WHERE iid = ".$this->get_id();
5617
        $res = Database::query($sql);
5618
        if (Database::num_rows($res) > 0) {
5619
            $row = Database::fetch_array($res);
5620
            $force = $row['force_commit'];
5621
            if ($force == 1) {
5622
                $force = 0;
5623
                $force_return = false;
5624
            } elseif ($force == 0) {
5625
                $force = 1;
5626
                $force_return = true;
5627
            }
5628
            $sql = "UPDATE $lp_table SET force_commit = $force
5629
                    WHERE iid = ".$this->get_id();
5630
            Database::query($sql);
5631
            $this->force_commit = $force_return;
5632
5633
            return $force_return;
5634
        }
5635
5636
        return -1;
5637
    }
5638
5639
    /**
5640
     * Updates the order of learning paths (goes through all of them by order and fills the gaps).
5641
     *
5642
     * @return bool True on success, false on failure
5643
     */
5644
    public function update_display_order()
5645
    {
5646
        $course_id = api_get_course_int_id();
5647
        $table = Database::get_course_table(TABLE_LP_MAIN);
5648
        $sql = "SELECT * FROM $table
5649
                WHERE c_id = $course_id
5650
                ORDER BY display_order";
5651
        $res = Database::query($sql);
5652
        if ($res === false) {
5653
            return false;
5654
        }
5655
5656
        $num = Database::num_rows($res);
5657
        // First check the order is correct, globally (might be wrong because
5658
        // of versions < 1.8.4).
5659
        if ($num > 0) {
5660
            $i = 1;
5661
            while ($row = Database::fetch_array($res)) {
5662
                if ($row['display_order'] != $i) {
5663
                    // If we find a gap in the order, we need to fix it.
5664
                    $sql = "UPDATE $table SET display_order = $i
5665
                            WHERE iid = ".$row['iid'];
5666
                    Database::query($sql);
5667
                }
5668
                $i++;
5669
            }
5670
        }
5671
5672
        return true;
5673
    }
5674
5675
    /**
5676
     * Updates the "prevent_reinit" value that enables control on reinitialising items on second view.
5677
     *
5678
     * @return bool True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case)
5679
     */
5680
    public function update_reinit()
5681
    {
5682
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5683
        $sql = "SELECT * FROM $lp_table
5684
                WHERE iid = ".$this->get_id();
5685
        $res = Database::query($sql);
5686
        if (Database::num_rows($res) > 0) {
5687
            $row = Database::fetch_array($res);
5688
            $force = $row['prevent_reinit'];
5689
            if ($force == 1) {
5690
                $force = 0;
5691
            } elseif ($force == 0) {
5692
                $force = 1;
5693
            }
5694
            $sql = "UPDATE $lp_table SET prevent_reinit = $force
5695
                    WHERE iid = ".$this->get_id();
5696
            Database::query($sql);
5697
            $this->prevent_reinit = $force;
5698
5699
            return $force;
5700
        }
5701
5702
        return -1;
5703
    }
5704
5705
    /**
5706
     * Determine the attempt_mode thanks to prevent_reinit and seriousgame_mode db flag.
5707
     *
5708
     * @return string 'single', 'multi' or 'seriousgame'
5709
     *
5710
     * @author ndiechburg <[email protected]>
5711
     */
5712
    public function get_attempt_mode()
5713
    {
5714
        //Set default value for seriousgame_mode
5715
        if (!isset($this->seriousgame_mode)) {
5716
            $this->seriousgame_mode = 0;
5717
        }
5718
        // Set default value for prevent_reinit
5719
        if (!isset($this->prevent_reinit)) {
5720
            $this->prevent_reinit = 1;
5721
        }
5722
        if ($this->seriousgame_mode == 1 && $this->prevent_reinit == 1) {
5723
            return 'seriousgame';
5724
        }
5725
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 1) {
5726
            return 'single';
5727
        }
5728
        if ($this->seriousgame_mode == 0 && $this->prevent_reinit == 0) {
5729
            return 'multiple';
5730
        }
5731
5732
        return 'single';
5733
    }
5734
5735
    /**
5736
     * Register the attempt mode into db thanks to flags prevent_reinit and seriousgame_mode flags.
5737
     *
5738
     * @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...
5739
     *
5740
     * @return bool
5741
     *
5742
     * @author ndiechburg <[email protected]>
5743
     */
5744
    public function set_attempt_mode($mode)
5745
    {
5746
        switch ($mode) {
5747
            case 'seriousgame':
5748
                $sg_mode = 1;
5749
                $prevent_reinit = 1;
5750
                break;
5751
            case 'single':
5752
                $sg_mode = 0;
5753
                $prevent_reinit = 1;
5754
                break;
5755
            case 'multiple':
5756
                $sg_mode = 0;
5757
                $prevent_reinit = 0;
5758
                break;
5759
            default:
5760
                $sg_mode = 0;
5761
                $prevent_reinit = 0;
5762
                break;
5763
        }
5764
        $this->prevent_reinit = $prevent_reinit;
5765
        $this->seriousgame_mode = $sg_mode;
5766
        $table = Database::get_course_table(TABLE_LP_MAIN);
5767
        $sql = "UPDATE $table SET
5768
                prevent_reinit = $prevent_reinit ,
5769
                seriousgame_mode = $sg_mode
5770
                WHERE iid = ".$this->get_id();
5771
        $res = Database::query($sql);
5772
        if ($res) {
5773
            return true;
5774
        } else {
5775
            return false;
5776
        }
5777
    }
5778
5779
    /**
5780
     * Switch between multiple attempt, single attempt or serious_game mode (only for scorm).
5781
     *
5782
     * @author ndiechburg <[email protected]>
5783
     */
5784
    public function switch_attempt_mode()
5785
    {
5786
        $mode = $this->get_attempt_mode();
5787
        switch ($mode) {
5788
            case 'single':
5789
                $next_mode = 'multiple';
5790
                break;
5791
            case 'multiple':
5792
                $next_mode = 'seriousgame';
5793
                break;
5794
            case 'seriousgame':
5795
            default:
5796
                $next_mode = 'single';
5797
                break;
5798
        }
5799
        $this->set_attempt_mode($next_mode);
5800
    }
5801
5802
    /**
5803
     * Switch the lp in ktm mode. This is a special scorm mode with unique attempt
5804
     * but possibility to do again a completed item.
5805
     *
5806
     * @return bool true if seriousgame_mode has been set to 1, false otherwise
5807
     *
5808
     * @author ndiechburg <[email protected]>
5809
     */
5810
    public function set_seriousgame_mode()
5811
    {
5812
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5813
        $sql = "SELECT * FROM $lp_table
5814
                WHERE iid = ".$this->get_id();
5815
        $res = Database::query($sql);
5816
        if (Database::num_rows($res) > 0) {
5817
            $row = Database::fetch_array($res);
5818
            $force = $row['seriousgame_mode'];
5819
            if ($force == 1) {
5820
                $force = 0;
5821
            } elseif ($force == 0) {
5822
                $force = 1;
5823
            }
5824
            $sql = "UPDATE $lp_table SET seriousgame_mode = $force
5825
			        WHERE iid = ".$this->get_id();
5826
            Database::query($sql);
5827
            $this->seriousgame_mode = $force;
5828
5829
            return $force;
5830
        }
5831
5832
        return -1;
5833
    }
5834
5835
    /**
5836
     * Updates the "scorm_debug" value that shows or hide the debug window.
5837
     *
5838
     * @return bool True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case)
5839
     */
5840
    public function update_scorm_debug()
5841
    {
5842
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
5843
        $sql = "SELECT * FROM $lp_table
5844
                WHERE iid = ".$this->get_id();
5845
        $res = Database::query($sql);
5846
        if (Database::num_rows($res) > 0) {
5847
            $row = Database::fetch_array($res);
5848
            $force = $row['debug'];
5849
            if ($force == 1) {
5850
                $force = 0;
5851
            } elseif ($force == 0) {
5852
                $force = 1;
5853
            }
5854
            $sql = "UPDATE $lp_table SET debug = $force
5855
                    WHERE iid = ".$this->get_id();
5856
            Database::query($sql);
5857
            $this->scorm_debug = $force;
5858
5859
            return $force;
5860
        }
5861
5862
        return -1;
5863
    }
5864
5865
    /**
5866
     * Function that makes a call to the function sort_tree_array and create_tree_array.
5867
     *
5868
     * @author Kevin Van Den Haute
5869
     *
5870
     * @param  array
5871
     */
5872
    public function tree_array($array)
5873
    {
5874
        $array = $this->sort_tree_array($array);
5875
        $this->create_tree_array($array);
5876
    }
5877
5878
    /**
5879
     * Creates an array with the elements of the learning path tree in it.
5880
     *
5881
     * @author Kevin Van Den Haute
5882
     *
5883
     * @param array $array
5884
     * @param int   $parent
5885
     * @param int   $depth
5886
     * @param array $tmp
5887
     */
5888
    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = [])
5889
    {
5890
        if (is_array($array)) {
5891
            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...
5892
                if ($array[$i]['parent_item_id'] == $parent) {
5893
                    if (!in_array($array[$i]['parent_item_id'], $tmp)) {
5894
                        $tmp[] = $array[$i]['parent_item_id'];
5895
                        $depth++;
5896
                    }
5897
                    $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']);
5898
                    $audio = isset($array[$i]['audio']) ? $array[$i]['audio'] : null;
5899
                    $path = isset($array[$i]['path']) ? $array[$i]['path'] : null;
5900
5901
                    $prerequisiteMinScore = isset($array[$i]['prerequisite_min_score']) ? $array[$i]['prerequisite_min_score'] : null;
5902
                    $prerequisiteMaxScore = isset($array[$i]['prerequisite_max_score']) ? $array[$i]['prerequisite_max_score'] : null;
5903
                    $ref = isset($array[$i]['ref']) ? $array[$i]['ref'] : '';
5904
                    $this->arrMenu[] = [
5905
                        'id' => $array[$i]['id'],
5906
                        'ref' => $ref,
5907
                        'item_type' => $array[$i]['item_type'],
5908
                        'title' => $array[$i]['title'],
5909
                        'title_raw' => $array[$i]['title_raw'],
5910
                        'path' => $path,
5911
                        'description' => $array[$i]['description'],
5912
                        'parent_item_id' => $array[$i]['parent_item_id'],
5913
                        'previous_item_id' => $array[$i]['previous_item_id'],
5914
                        'next_item_id' => $array[$i]['next_item_id'],
5915
                        'min_score' => $array[$i]['min_score'],
5916
                        'max_score' => $array[$i]['max_score'],
5917
                        'mastery_score' => $array[$i]['mastery_score'],
5918
                        'display_order' => $array[$i]['display_order'],
5919
                        'prerequisite' => $preq,
5920
                        'depth' => $depth,
5921
                        'audio' => $audio,
5922
                        'prerequisite_min_score' => $prerequisiteMinScore,
5923
                        'prerequisite_max_score' => $prerequisiteMaxScore,
5924
                    ];
5925
                    $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp);
5926
                }
5927
            }
5928
        }
5929
    }
5930
5931
    /**
5932
     * Sorts a multi dimensional array by parent id and display order.
5933
     *
5934
     * @author Kevin Van Den Haute
5935
     *
5936
     * @param array $array (array with al the learning path items in it)
5937
     *
5938
     * @return array
5939
     */
5940
    public function sort_tree_array($array)
5941
    {
5942
        foreach ($array as $key => $row) {
5943
            $parent[$key] = $row['parent_item_id'];
5944
            $position[$key] = $row['display_order'];
5945
        }
5946
5947
        if (count($array) > 0) {
5948
            array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array);
5949
        }
5950
5951
        return $array;
5952
    }
5953
5954
    /**
5955
     * Function that creates a html list of learning path items so that we can add audio files to them.
5956
     *
5957
     * @author Kevin Van Den Haute
5958
     *
5959
     * @return string
5960
     */
5961
    public function overview()
5962
    {
5963
        $return = '';
5964
        $update_audio = isset($_GET['updateaudio']) ? $_GET['updateaudio'] : null;
5965
5966
        // we need to start a form when we want to update all the mp3 files
5967
        if ($update_audio == 'true') {
5968
            $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">';
5969
        }
5970
        $return .= '<div id="message"></div>';
5971
        if (count($this->items) == 0) {
5972
            $return .= Display::return_message(get_lang('YouShouldAddItemsBeforeAttachAudio'), 'normal');
5973
        } else {
5974
            $return_audio = '<table class="table table-hover table-striped data_table">';
5975
            $return_audio .= '<tr>';
5976
            $return_audio .= '<th width="40%">'.get_lang('Title').'</th>';
5977
            $return_audio .= '<th>'.get_lang('Audio').'</th>';
5978
            $return_audio .= '</tr>';
5979
5980
            if ($update_audio != 'true') {
5981
                $return .= '<div class="col-md-12">';
5982
                $return .= self::return_new_tree($update_audio);
5983
                $return .= '</div>';
5984
                $return .= Display::div(
5985
                    Display::url(get_lang('Save'), '#', ['id' => 'listSubmit', 'class' => 'btn btn-primary']),
5986
                    ['style' => 'float:left; margin-top:15px;width:100%']
5987
                );
5988
            } else {
5989
                $return_audio .= self::return_new_tree($update_audio);
5990
                $return .= $return_audio.'</table>';
5991
            }
5992
5993
            // We need to close the form when we are updating the mp3 files.
5994
            if ($update_audio == 'true') {
5995
                $return .= '<div class="footer-audio">';
5996
                $return .= Display::button(
5997
                    'save_audio',
5998
                    '<em class="fa fa-file-audio-o"></em> '.get_lang('SaveAudioAndOrganization'),
5999
                    ['class' => 'btn btn-primary', 'type' => 'submit']
6000
                );
6001
                $return .= '</div>';
6002
            }
6003
        }
6004
6005
        // We need to close the form when we are updating the mp3 files.
6006
        if ($update_audio == 'true' && isset($this->arrMenu) && count($this->arrMenu) != 0) {
6007
            $return .= '</form>';
6008
        }
6009
6010
        return $return;
6011
    }
6012
6013
    /**
6014
     * @param string $update_audio
6015
     *
6016
     * @return array
6017
     */
6018
    public function processBuildMenuElements($update_audio = 'false')
6019
    {
6020
        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
6021
        $arrLP = $this->getItemsForForm();
6022
6023
        $this->tree_array($arrLP);
6024
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
6025
        unset($this->arrMenu);
6026
        $default_data = null;
6027
        $default_content = null;
6028
        $elements = [];
6029
        $return_audio = null;
6030
        $iconPath = api_get_path(SYS_CODE_PATH).'img/';
6031
        $mainUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq();
6032
        $countItems = count($arrLP);
6033
6034
        $upIcon = Display::return_icon(
6035
            'up.png',
6036
            get_lang('Up'),
6037
            [],
6038
            ICON_SIZE_TINY
6039
        );
6040
6041
        $disableUpIcon = Display::return_icon(
6042
            'up_na.png',
6043
            get_lang('Up'),
6044
            [],
6045
            ICON_SIZE_TINY
6046
        );
6047
6048
        $downIcon = Display::return_icon(
6049
            'down.png',
6050
            get_lang('Down'),
6051
            [],
6052
            ICON_SIZE_TINY
6053
        );
6054
6055
        $disableDownIcon = Display::return_icon(
6056
            'down_na.png',
6057
            get_lang('Down'),
6058
            [],
6059
            ICON_SIZE_TINY
6060
        );
6061
6062
        $show = api_get_configuration_value('show_full_lp_item_title_in_edition');
6063
6064
        $pluginCalendar = api_get_plugin_setting('learning_calendar', 'enabled') === 'true';
6065
        $plugin = null;
6066
        if ($pluginCalendar) {
6067
            $plugin = LearningCalendarPlugin::create();
6068
        }
6069
6070
        for ($i = 0; $i < $countItems; $i++) {
6071
            $parent_id = $arrLP[$i]['parent_item_id'];
6072
            $title = $arrLP[$i]['title'];
6073
            $title_cut = $arrLP[$i]['title_raw'];
6074
            if ($show === false) {
6075
                $title_cut = cut($arrLP[$i]['title'], self::MAX_LP_ITEM_TITLE_LENGTH);
6076
            }
6077
            // Link for the documents
6078
            if ($arrLP[$i]['item_type'] === 'document' || $arrLP[$i]['item_type'] == TOOL_READOUT_TEXT) {
6079
                $url = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6080
                $title_cut = Display::url(
6081
                    $title_cut,
6082
                    $url,
6083
                    [
6084
                        'class' => 'ajax moved',
6085
                        'data-title' => $title,
6086
                        'title' => $title,
6087
                    ]
6088
                );
6089
            }
6090
6091
            // Detect if type is FINAL_ITEM to set path_id to SESSION
6092
            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6093
                Session::write('pathItem', $arrLP[$i]['path']);
6094
            }
6095
6096
            $oddClass = 'row_even';
6097
            if (($i % 2) == 0) {
6098
                $oddClass = 'row_odd';
6099
            }
6100
            $return_audio .= '<tr id ="lp_item_'.$arrLP[$i]['id'].'" class="'.$oddClass.'">';
6101
            $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']);
6102
6103
            if (file_exists($iconPath.'lp_'.$icon_name.'.png')) {
6104
                $icon = Display::return_icon('lp_'.$icon_name.'.png');
6105
            } else {
6106
                if (file_exists($iconPath.'lp_'.$icon_name.'.gif')) {
6107
                    $icon = Display::return_icon('lp_'.$icon_name.'.gif');
6108
                } else {
6109
                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
6110
                        $icon = Display::return_icon('certificate.png');
6111
                    } else {
6112
                        $icon = Display::return_icon('folder_document.gif');
6113
                    }
6114
                }
6115
            }
6116
6117
            // The audio column.
6118
            $return_audio .= '<td align="left" style="padding-left:10px;">';
6119
            $audio = '';
6120
            if (!$update_audio || $update_audio != 'true') {
6121
                if (empty($arrLP[$i]['audio'])) {
6122
                    $audio .= '';
6123
                }
6124
            } else {
6125
                $types = self::getChapterTypes();
6126
                if (!in_array($arrLP[$i]['item_type'], $types)) {
6127
                    $audio .= '<input type="file" name="mp3file'.$arrLP[$i]['id'].'" id="mp3file" />';
6128
                    if (!empty($arrLP[$i]['audio'])) {
6129
                        $audio .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<br />
6130
                        <input type="checkbox" name="removemp3'.$arrLP[$i]['id'].'" id="checkbox'.$arrLP[$i]['id'].'" />'.get_lang('RemoveAudio');
6131
                    }
6132
                }
6133
            }
6134
6135
            $return_audio .= Display::span($icon.' '.$title).
6136
                Display::tag(
6137
                    'td',
6138
                    $audio,
6139
                    ['style' => '']
6140
                );
6141
            $return_audio .= '</td>';
6142
            $move_icon = '';
6143
            $move_item_icon = '';
6144
            $edit_icon = '';
6145
            $delete_icon = '';
6146
            $audio_icon = '';
6147
            $prerequisities_icon = '';
6148
            $forumIcon = '';
6149
            $previewIcon = '';
6150
            $pluginCalendarIcon = '';
6151
            $orderIcons = '';
6152
            $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'learning_calendar/start.php?';
6153
6154
            if ($is_allowed_to_edit) {
6155
                if (!$update_audio || $update_audio != 'true') {
6156
                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
6157
                        $move_icon .= '<a class="moved" href="#">';
6158
                        $move_icon .= Display::return_icon(
6159
                            'move_everywhere.png',
6160
                            get_lang('Move'),
6161
                            [],
6162
                            ICON_SIZE_TINY
6163
                        );
6164
                        $move_icon .= '</a>';
6165
                    }
6166
                }
6167
6168
                // No edit for this item types
6169
                if (!in_array($arrLP[$i]['item_type'], ['sco', 'asset', 'final_item'])) {
6170
                    if ($arrLP[$i]['item_type'] != 'dir') {
6171
                        $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">';
6172
                        $edit_icon .= Display::return_icon(
6173
                            'edit.png',
6174
                            get_lang('LearnpathEditModule'),
6175
                            [],
6176
                            ICON_SIZE_TINY
6177
                        );
6178
                        $edit_icon .= '</a>';
6179
6180
                        if (!in_array($arrLP[$i]['item_type'], ['forum', 'thread'])) {
6181
                            $forumThread = null;
6182
                            if (isset($this->items[$arrLP[$i]['id']])) {
6183
                                $forumThread = $this->items[$arrLP[$i]['id']]->getForumThread(
6184
                                    $this->course_int_id,
6185
                                    $this->lp_session_id
6186
                                );
6187
                            }
6188
                            if ($forumThread) {
6189
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6190
                                        'action' => 'dissociate_forum',
6191
                                        'id' => $arrLP[$i]['id'],
6192
                                        'lp_id' => $this->lp_id,
6193
                                    ]);
6194
                                $forumIcon = Display::url(
6195
                                    Display::return_icon(
6196
                                        'forum.png',
6197
                                        get_lang('DissociateForumToLPItem'),
6198
                                        [],
6199
                                        ICON_SIZE_TINY
6200
                                    ),
6201
                                    $forumIconUrl,
6202
                                    ['class' => 'btn btn-default lp-btn-dissociate-forum']
6203
                                );
6204
                            } else {
6205
                                $forumIconUrl = $mainUrl.'&'.http_build_query([
6206
                                        'action' => 'create_forum',
6207
                                        'id' => $arrLP[$i]['id'],
6208
                                        'lp_id' => $this->lp_id,
6209
                                    ]);
6210
                                $forumIcon = Display::url(
6211
                                    Display::return_icon(
6212
                                        'forum.png',
6213
                                        get_lang('AssociateForumToLPItem'),
6214
                                        [],
6215
                                        ICON_SIZE_TINY
6216
                                    ),
6217
                                    $forumIconUrl,
6218
                                    ['class' => 'btn btn-default lp-btn-associate-forum']
6219
                                );
6220
                            }
6221
                        }
6222
                    } else {
6223
                        $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">';
6224
                        $edit_icon .= Display::return_icon(
6225
                            'edit.png',
6226
                            get_lang('LearnpathEditModule'),
6227
                            [],
6228
                            ICON_SIZE_TINY
6229
                        );
6230
                        $edit_icon .= '</a>';
6231
                    }
6232
                } else {
6233
                    if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
6234
                        $edit_icon .= '<a href="'.$mainUrl.'&action=edit_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'" class="btn btn-default">';
6235
                        $edit_icon .= Display::return_icon(
6236
                            'edit.png',
6237
                            get_lang('Edit'),
6238
                            [],
6239
                            ICON_SIZE_TINY
6240
                        );
6241
                        $edit_icon .= '</a>';
6242
                    }
6243
                }
6244
6245
                if ($pluginCalendar) {
6246
                    $pluginLink = $pluginUrl.
6247
                        '&action=toggle_visibility&lp_item_id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6248
                    $iconCalendar = Display::return_icon('agenda_na.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6249
                    $itemInfo = $plugin->getItemVisibility($arrLP[$i]['id']);
6250
                    if ($itemInfo && $itemInfo['value'] == 1) {
6251
                        $iconCalendar = Display::return_icon('agenda.png', get_lang('OneDay'), [], ICON_SIZE_TINY);
6252
                    }
6253
                    $pluginCalendarIcon = Display::url(
6254
                        $iconCalendar,
6255
                        $pluginLink,
6256
                        ['class' => 'btn btn-default']
6257
                    );
6258
                }
6259
6260
                if ($arrLP[$i]['item_type'] != 'final_item') {
6261
                    $orderIcons = Display::url(
6262
                        $upIcon,
6263
                        'javascript:void(0)',
6264
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'up', 'data-id' => $arrLP[$i]['id']]
6265
                    );
6266
                    $orderIcons .= Display::url(
6267
                        $downIcon,
6268
                        'javascript:void(0)',
6269
                        ['class' => 'btn btn-default order_items', 'data-dir' => 'down', 'data-id' => $arrLP[$i]['id']]
6270
                    );
6271
                }
6272
6273
                $delete_icon .= ' <a
6274
                    href="'.$mainUrl.'&action=delete_item&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id.'"
6275
                    onclick="return confirmation(\''.addslashes($title).'\');"
6276
                    class="btn btn-default">';
6277
                $delete_icon .= Display::return_icon(
6278
                    'delete.png',
6279
                    get_lang('LearnpathDeleteModule'),
6280
                    [],
6281
                    ICON_SIZE_TINY
6282
                );
6283
                $delete_icon .= '</a>';
6284
6285
                $url = $mainUrl.'&view=build&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6286
                $previewImage = Display::return_icon(
6287
                    'preview_view.png',
6288
                    get_lang('Preview'),
6289
                    [],
6290
                    ICON_SIZE_TINY
6291
                );
6292
6293
                switch ($arrLP[$i]['item_type']) {
6294
                    case TOOL_DOCUMENT:
6295
                    case TOOL_LP_FINAL_ITEM:
6296
                    case TOOL_READOUT_TEXT:
6297
                        $urlPreviewLink = $mainUrl.'&action=view_item&mode=preview_document&id='.$arrLP[$i]['id'].'&lp_id='.$this->lp_id;
6298
                        $previewIcon = Display::url(
6299
                            $previewImage,
6300
                            $urlPreviewLink,
6301
                            [
6302
                                'target' => '_blank',
6303
                                'class' => 'btn btn-default',
6304
                                'data-title' => $arrLP[$i]['title'],
6305
                                'title' => $arrLP[$i]['title'],
6306
                            ]
6307
                        );
6308
                        break;
6309
                    case TOOL_THREAD:
6310
                    case TOOL_FORUM:
6311
                    case TOOL_QUIZ:
6312
                    case TOOL_STUDENTPUBLICATION:
6313
                    case TOOL_LP_FINAL_ITEM:
6314
                    case TOOL_LINK:
6315
                        $class = 'btn btn-default';
6316
                        $target = '_blank';
6317
                        $link = self::rl_get_resource_link_for_learnpath(
6318
                            $this->course_int_id,
6319
                            $this->lp_id,
6320
                            $arrLP[$i]['id'],
6321
                            0
6322
                        );
6323
                        $previewIcon = Display::url(
6324
                            $previewImage,
6325
                            $link,
6326
                            [
6327
                                'class' => $class,
6328
                                'data-title' => $arrLP[$i]['title'],
6329
                                'title' => $arrLP[$i]['title'],
6330
                                'target' => $target,
6331
                            ]
6332
                        );
6333
                        break;
6334
                    default:
6335
                        $previewIcon = Display::url(
6336
                            $previewImage,
6337
                            $url.'&action=view_item',
6338
                            ['class' => 'btn btn-default', 'target' => '_blank']
6339
                        );
6340
                        break;
6341
                }
6342
6343
                if ($arrLP[$i]['item_type'] != 'dir') {
6344
                    $prerequisities_icon = Display::url(
6345
                        Display::return_icon(
6346
                            'accept.png',
6347
                            get_lang('LearnpathPrerequisites'),
6348
                            [],
6349
                            ICON_SIZE_TINY
6350
                        ),
6351
                        $url.'&action=edit_item_prereq',
6352
                        ['class' => 'btn btn-default']
6353
                    );
6354
                    if ($arrLP[$i]['item_type'] != 'final_item') {
6355
                        $move_item_icon = Display::url(
6356
                            Display::return_icon(
6357
                                'move.png',
6358
                                get_lang('Move'),
6359
                                [],
6360
                                ICON_SIZE_TINY
6361
                            ),
6362
                            $url.'&action=move_item',
6363
                            ['class' => 'btn btn-default']
6364
                        );
6365
                    }
6366
                    $audio_icon = Display::url(
6367
                        Display::return_icon(
6368
                            'audio.png',
6369
                            get_lang('UplUpload'),
6370
                            [],
6371
                            ICON_SIZE_TINY
6372
                        ),
6373
                        $url.'&action=add_audio',
6374
                        ['class' => 'btn btn-default']
6375
                    );
6376
                }
6377
            }
6378
            if ($update_audio != 'true') {
6379
                $row = $move_icon.' '.$icon.
6380
                    Display::span($title_cut).
6381
                    Display::tag(
6382
                        'div',
6383
                        "<div class=\"btn-group btn-group-xs\">
6384
                                    $previewIcon
6385
                                    $audio
6386
                                    $edit_icon
6387
                                    $pluginCalendarIcon
6388
                                    $forumIcon
6389
                                    $prerequisities_icon
6390
                                    $move_item_icon
6391
                                    $audio_icon
6392
                                    $orderIcons
6393
                                    $delete_icon
6394
                                </div>",
6395
                        ['class' => 'btn-toolbar button_actions']
6396
                    );
6397
            } else {
6398
                $row =
6399
                    Display::span($title.$icon).
6400
                    Display::span($audio, ['class' => 'button_actions']);
6401
            }
6402
6403
            $default_data[$arrLP[$i]['id']] = $row;
6404
            $default_content[$arrLP[$i]['id']] = $arrLP[$i];
6405
6406
            if (empty($parent_id)) {
6407
                $elements[$arrLP[$i]['id']]['data'] = $row;
6408
                $elements[$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6409
            } else {
6410
                $parent_arrays = [];
6411
                if ($arrLP[$i]['depth'] > 1) {
6412
                    // Getting list of parents
6413
                    for ($j = 0; $j < $arrLP[$i]['depth']; $j++) {
6414
                        foreach ($arrLP as $item) {
6415
                            if ($item['id'] == $parent_id) {
6416
                                if ($item['parent_item_id'] == 0) {
6417
                                    $parent_id = $item['id'];
6418
                                    break;
6419
                                } else {
6420
                                    $parent_id = $item['parent_item_id'];
6421
                                    if (empty($parent_arrays)) {
6422
                                        $parent_arrays[] = intval($item['id']);
6423
                                    }
6424
                                    $parent_arrays[] = $parent_id;
6425
                                    break;
6426
                                }
6427
                            }
6428
                        }
6429
                    }
6430
                }
6431
6432
                if (!empty($parent_arrays)) {
6433
                    $parent_arrays = array_reverse($parent_arrays);
6434
                    $val = '$elements';
6435
                    $x = 0;
6436
                    foreach ($parent_arrays as $item) {
6437
                        if ($x != count($parent_arrays) - 1) {
6438
                            $val .= '["'.$item.'"]["children"]';
6439
                        } else {
6440
                            $val .= '["'.$item.'"]["children"]';
6441
                        }
6442
                        $x++;
6443
                    }
6444
                    $val .= "";
6445
                    $code_str = $val."[".$arrLP[$i]['id']."][\"load_data\"] = '".$arrLP[$i]['id']."' ; ";
6446
                    eval($code_str);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6447
                } else {
6448
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['data'] = $row;
6449
                    $elements[$parent_id]['children'][$arrLP[$i]['id']]['type'] = $arrLP[$i]['item_type'];
6450
                }
6451
            }
6452
        }
6453
6454
        return [
6455
            'elements' => $elements,
6456
            'default_data' => $default_data,
6457
            'default_content' => $default_content,
6458
            'return_audio' => $return_audio,
6459
        ];
6460
    }
6461
6462
    /**
6463
     * @param string $updateAudio true/false strings
6464
     *
6465
     * @return string
6466
     */
6467
    public function returnLpItemList($updateAudio)
6468
    {
6469
        $result = $this->processBuildMenuElements($updateAudio);
6470
6471
        $html = self::print_recursive(
6472
            $result['elements'],
6473
            $result['default_data'],
6474
            $result['default_content']
6475
        );
6476
6477
        if (!empty($html)) {
6478
            $html .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6479
        }
6480
6481
        return $html;
6482
    }
6483
6484
    /**
6485
     * @param string $update_audio
6486
     * @param bool   $drop_element_here
6487
     *
6488
     * @return string
6489
     */
6490
    public function return_new_tree($update_audio = 'false', $drop_element_here = false)
6491
    {
6492
        $result = $this->processBuildMenuElements($update_audio);
6493
6494
        $list = '<ul id="lp_item_list">';
6495
        $tree = $this->print_recursive(
6496
            $result['elements'],
6497
            $result['default_data'],
6498
            $result['default_content']
6499
        );
6500
6501
        if (!empty($tree)) {
6502
            $list .= $tree;
6503
        } else {
6504
            if ($drop_element_here) {
6505
                $list .= Display::return_message(get_lang('DragAndDropAnElementHere'));
6506
            }
6507
        }
6508
        $list .= '</ul>';
6509
6510
        $return = Display::panelCollapse(
6511
            $this->name,
6512
            $list,
6513
            'scorm-list',
6514
            null,
6515
            'scorm-list-accordion',
6516
            'scorm-list-collapse'
6517
        );
6518
6519
        if ($update_audio === 'true') {
6520
            $return = $result['return_audio'];
6521
        }
6522
6523
        return $return;
6524
    }
6525
6526
    /**
6527
     * @param array $elements
6528
     * @param array $default_data
6529
     * @param array $default_content
6530
     *
6531
     * @return string
6532
     */
6533
    public function print_recursive($elements, $default_data, $default_content)
6534
    {
6535
        $return = '';
6536
        foreach ($elements as $key => $item) {
6537
            if (isset($item['load_data']) || empty($item['data'])) {
6538
                $item['data'] = $default_data[$item['load_data']];
6539
                $item['type'] = $default_content[$item['load_data']]['item_type'];
6540
            }
6541
            $sub_list = '';
6542
            if (isset($item['type']) && $item['type'] === 'dir') {
6543
                // empty value
6544
                $sub_list = Display::tag('li', '', ['class' => 'sub_item empty']);
6545
            }
6546
            if (empty($item['children'])) {
6547
                $sub_list = Display::tag('ul', $sub_list, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6548
                $active = null;
6549
                if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
6550
                    $active = 'active';
6551
                }
6552
                $return .= Display::tag(
6553
                    'li',
6554
                    Display::div($item['data'], ['class' => "item_data $active"]).$sub_list,
6555
                    ['id' => $key, 'class' => 'record li_container']
6556
                );
6557
            } else {
6558
                // Sections
6559
                $data = '';
6560
                if (isset($item['children'])) {
6561
                    $data = self::print_recursive($item['children'], $default_data, $default_content);
6562
                }
6563
                $sub_list = Display::tag('ul', $sub_list.$data, ['id' => 'UL_'.$key, 'class' => 'record li_container']);
6564
                $return .= Display::tag(
6565
                    'li',
6566
                    Display::div($item['data'], ['class' => 'item_data']).$sub_list,
6567
                    ['id' => $key, 'class' => 'record li_container']
6568
                );
6569
            }
6570
        }
6571
6572
        return $return;
6573
    }
6574
6575
    /**
6576
     * This function builds the action menu.
6577
     *
6578
     * @param bool   $returnString           Optional
6579
     * @param bool   $showRequirementButtons Optional. Allow show the requirements button
6580
     * @param bool   $isConfigPage           Optional. If is the config page, show the edit button
6581
     * @param bool   $allowExpand            Optional. Allow show the expand/contract button
6582
     * @param string $action
6583
     *
6584
     * @return string
6585
     */
6586
    public function build_action_menu(
6587
        $returnString = false,
6588
        $showRequirementButtons = true,
6589
        $isConfigPage = false,
6590
        $allowExpand = true,
6591
        $action = ''
6592
    ) {
6593
        $actionsRight = '';
6594
        $lpId = $this->lp_id;
6595
        $back = Display::url(
6596
            Display::return_icon(
6597
                'back.png',
6598
                get_lang('ReturnToLearningPaths'),
6599
                '',
6600
                ICON_SIZE_MEDIUM
6601
            ),
6602
            'lp_controller.php?'.api_get_cidreq()
6603
        );
6604
6605
        /*if ($backToBuild) {
6606
            $back = Display::url(
6607
                Display::return_icon(
6608
                    'back.png',
6609
                    get_lang('GoBack'),
6610
                    '',
6611
                    ICON_SIZE_MEDIUM
6612
                ),
6613
                "lp_controller.php?action=add_item&type=step&lp_id=$lpId&".api_get_cidreq()
6614
            );
6615
        }*/
6616
6617
        $actionsLeft = $back;
6618
6619
        $actionsLeft .= Display::url(
6620
            Display::return_icon(
6621
                'preview_view.png',
6622
                get_lang('Preview'),
6623
                '',
6624
                ICON_SIZE_MEDIUM
6625
            ),
6626
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6627
                'action' => 'view',
6628
                'lp_id' => $lpId,
6629
                'isStudentView' => 'true',
6630
            ])
6631
        );
6632
6633
        $actionsLeft .= Display::url(
6634
            Display::return_icon(
6635
                'upload_audio.png',
6636
                get_lang('UpdateAllAudioFragments'),
6637
                '',
6638
                ICON_SIZE_MEDIUM
6639
            ),
6640
            'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6641
                'action' => 'admin_view',
6642
                'lp_id' => $lpId,
6643
                'updateaudio' => 'true',
6644
            ])
6645
        );
6646
6647
        $subscriptionSettings = self::getSubscriptionSettings();
6648
6649
        $request = api_request_uri();
6650
        if (strpos($request, 'edit') === false) {
6651
            $actionsLeft .= Display::url(
6652
                Display::return_icon(
6653
                    'settings.png',
6654
                    get_lang('CourseSettings'),
6655
                    '',
6656
                    ICON_SIZE_MEDIUM
6657
                ),
6658
                'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6659
                    'action' => 'edit',
6660
                    'lp_id' => $lpId,
6661
                ])
6662
            );
6663
        }
6664
6665
        if ((strpos($request, 'build') === false &&
6666
            strpos($request, 'add_item') === false) ||
6667
            in_array($action, ['add_audio'])
6668
        ) {
6669
            $actionsLeft .= Display::url(
6670
                Display::return_icon(
6671
                    'edit.png',
6672
                    get_lang('Edit'),
6673
                    '',
6674
                    ICON_SIZE_MEDIUM
6675
                ),
6676
                'lp_controller.php?'.http_build_query([
6677
                    'action' => 'build',
6678
                    'lp_id' => $lpId,
6679
                ]).'&'.api_get_cidreq()
6680
            );
6681
        }
6682
6683
        if (strpos(api_get_self(), 'lp_subscribe_users.php') === false) {
6684
            if ($this->subscribeUsers == 1 &&
6685
                $subscriptionSettings['allow_add_users_to_lp']) {
6686
                $actionsLeft .= Display::url(
6687
                    Display::return_icon(
6688
                        'user.png',
6689
                        get_lang('SubscribeUsersToLp'),
6690
                        '',
6691
                        ICON_SIZE_MEDIUM
6692
                    ),
6693
                    api_get_path(WEB_CODE_PATH)."lp/lp_subscribe_users.php?lp_id=".$lpId."&".api_get_cidreq()
6694
                );
6695
            }
6696
        }
6697
6698
        if ($allowExpand) {
6699
            $actionsLeft .= Display::url(
6700
                Display::return_icon(
6701
                    'expand.png',
6702
                    get_lang('Expand'),
6703
                    ['id' => 'expand'],
6704
                    ICON_SIZE_MEDIUM
6705
                ).
6706
                Display::return_icon(
6707
                    'contract.png',
6708
                    get_lang('Collapse'),
6709
                    ['id' => 'contract', 'class' => 'hide'],
6710
                    ICON_SIZE_MEDIUM
6711
                ),
6712
                '#',
6713
                ['role' => 'button', 'id' => 'hide_bar_template']
6714
            );
6715
        }
6716
6717
        if ($showRequirementButtons) {
6718
            $buttons = [
6719
                [
6720
                    'title' => get_lang('SetPrerequisiteForEachItem'),
6721
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6722
                        'action' => 'set_previous_step_as_prerequisite',
6723
                        'lp_id' => $lpId,
6724
                    ]),
6725
                ],
6726
                [
6727
                    'title' => get_lang('ClearAllPrerequisites'),
6728
                    'href' => 'lp_controller.php?'.api_get_cidreq().'&'.http_build_query([
6729
                        'action' => 'clear_prerequisites',
6730
                        'lp_id' => $lpId,
6731
                    ]),
6732
                ],
6733
            ];
6734
            $actionsRight = Display::groupButtonWithDropDown(
6735
                get_lang('PrerequisitesOptions'),
6736
                $buttons,
6737
                true
6738
            );
6739
        }
6740
6741
        $toolbar = Display::toolbarAction(
6742
            'actions-lp-controller',
6743
            [$actionsLeft, $actionsRight]
6744
        );
6745
6746
        if ($returnString) {
6747
            return $toolbar;
6748
        }
6749
6750
        echo $toolbar;
6751
    }
6752
6753
    /**
6754
     * Creates the default learning path folder.
6755
     *
6756
     * @param array $course
6757
     * @param int   $creatorId
6758
     *
6759
     * @return bool
6760
     */
6761
    public static function generate_learning_path_folder($course, $creatorId = 0)
6762
    {
6763
        // Creating learning_path folder
6764
        $dir = '/learning_path';
6765
        $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6766
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6767
6768
        $folder = false;
6769
        if (!is_dir($filepath.'/'.$dir)) {
6770
            $folderData = create_unexisting_directory(
6771
                $course,
6772
                $creatorId,
6773
                0,
6774
                null,
6775
                0,
6776
                $filepath,
6777
                $dir,
6778
                get_lang('LearningPaths'),
6779
                0
6780
            );
6781
            if (!empty($folderData)) {
6782
                $folder = true;
6783
            }
6784
        } else {
6785
            $folder = true;
6786
        }
6787
6788
        return $folder;
6789
    }
6790
6791
    /**
6792
     * @param array  $course
6793
     * @param string $lp_name
6794
     * @param int    $creatorId
6795
     *
6796
     * @return array
6797
     */
6798
    public function generate_lp_folder($course, $lp_name = '', $creatorId = 0)
6799
    {
6800
        $filepath = '';
6801
        $dir = '/learning_path/';
6802
6803
        if (empty($lp_name)) {
6804
            $lp_name = $this->name;
6805
        }
6806
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6807
        $folder = self::generate_learning_path_folder($course, $creatorId);
6808
6809
        // Limits title size
6810
        $title = api_substr(api_replace_dangerous_char($lp_name), 0, 80);
6811
        $dir = $dir.$title;
6812
6813
        // Creating LP folder
6814
        $documentId = null;
6815
        if ($folder) {
6816
            $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document';
6817
            if (!is_dir($filepath.'/'.$dir)) {
6818
                $folderData = create_unexisting_directory(
6819
                    $course,
6820
                    $creatorId,
6821
                    0,
6822
                    0,
6823
                    0,
6824
                    $filepath,
6825
                    $dir,
6826
                    $lp_name
6827
                );
6828
                if (!empty($folderData)) {
6829
                    $folder = true;
6830
                }
6831
6832
                $documentId = $folderData['id'];
6833
            } else {
6834
                $folder = true;
6835
            }
6836
            $dir = $dir.'/';
6837
            if ($folder) {
6838
                $filepath = api_get_path(SYS_COURSE_PATH).$course['path'].'/document'.$dir;
6839
            }
6840
        }
6841
6842
        if (empty($documentId)) {
6843
            $dir = api_remove_trailing_slash($dir);
6844
            $documentId = DocumentManager::get_document_id($course, $dir, 0);
6845
        }
6846
6847
        $array = [
6848
            'dir' => $dir,
6849
            'filepath' => $filepath,
6850
            'folder' => $folder,
6851
            'id' => $documentId,
6852
        ];
6853
6854
        return $array;
6855
    }
6856
6857
    /**
6858
     * Create a new document //still needs some finetuning.
6859
     *
6860
     * @param array  $courseInfo
6861
     * @param string $content
6862
     * @param string $title
6863
     * @param string $extension
6864
     * @param int    $parentId
6865
     * @param int    $creatorId  creator id
6866
     *
6867
     * @return int
6868
     */
6869
    public function create_document(
6870
        $courseInfo,
6871
        $content = '',
6872
        $title = '',
6873
        $extension = 'html',
6874
        $parentId = 0,
6875
        $creatorId = 0
6876
    ) {
6877
        if (!empty($courseInfo)) {
6878
            $course_id = $courseInfo['real_id'];
6879
        } else {
6880
            $course_id = api_get_course_int_id();
6881
        }
6882
6883
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
6884
        $sessionId = api_get_session_id();
6885
6886
        // Generates folder
6887
        $result = $this->generate_lp_folder($courseInfo);
6888
        $dir = $result['dir'];
6889
6890
        if (empty($parentId) || $parentId == '/') {
6891
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
6892
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
6893
6894
            if ($parentId === '/') {
6895
                $dir = '/';
6896
            }
6897
6898
            // Please, do not modify this dirname formatting.
6899
            if (strstr($dir, '..')) {
6900
                $dir = '/';
6901
            }
6902
6903
            if (!empty($dir[0]) && $dir[0] == '.') {
6904
                $dir = substr($dir, 1);
6905
            }
6906
            if (!empty($dir[0]) && $dir[0] != '/') {
6907
                $dir = '/'.$dir;
6908
            }
6909
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
6910
                $dir .= '/';
6911
            }
6912
        } else {
6913
            $parentInfo = DocumentManager::get_document_data_by_id(
6914
                $parentId,
6915
                $courseInfo['code']
6916
            );
6917
            if (!empty($parentInfo)) {
6918
                $dir = $parentInfo['path'].'/';
6919
            }
6920
        }
6921
6922
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6923
        if (!is_dir($filepath)) {
6924
            $dir = '/';
6925
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
6926
        }
6927
6928
        // stripslashes() before calling api_replace_dangerous_char() because $_POST['title']
6929
        // is already escaped twice when it gets here.
6930
        $originalTitle = !empty($title) ? $title : $_POST['title'];
6931
        if (!empty($title)) {
6932
            $title = api_replace_dangerous_char(stripslashes($title));
6933
        } else {
6934
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
6935
        }
6936
6937
        $title = disable_dangerous_file($title);
6938
        $filename = $title;
6939
        $content = !empty($content) ? $content : $_POST['content_lp'];
6940
        $tmp_filename = $filename;
6941
6942
        $i = 0;
6943
        while (file_exists($filepath.$tmp_filename.'.'.$extension)) {
6944
            $tmp_filename = $filename.'_'.++$i;
6945
        }
6946
6947
        $filename = $tmp_filename.'.'.$extension;
6948
        if ($extension == 'html') {
6949
            $content = stripslashes($content);
6950
            $content = str_replace(
6951
                api_get_path(WEB_COURSE_PATH),
6952
                api_get_path(REL_PATH).'courses/',
6953
                $content
6954
            );
6955
6956
            // Change the path of mp3 to absolute.
6957
            // The first regexp deals with :// urls.
6958
            $content = preg_replace(
6959
                "|(flashvars=\"file=)([^:/]+)/|",
6960
                "$1".api_get_path(
6961
                    REL_COURSE_PATH
6962
                ).$courseInfo['path'].'/document/',
6963
                $content
6964
            );
6965
            // The second regexp deals with audio/ urls.
6966
            $content = preg_replace(
6967
                "|(flashvars=\"file=)([^/]+)/|",
6968
                "$1".api_get_path(
6969
                    REL_COURSE_PATH
6970
                ).$courseInfo['path'].'/document/$2/',
6971
                $content
6972
            );
6973
            // For flv player: To prevent edition problem with firefox,
6974
            // we have to use a strange tip (don't blame me please).
6975
            $content = str_replace(
6976
                '</body>',
6977
                '<style type="text/css">body{}</style></body>',
6978
                $content
6979
            );
6980
        }
6981
6982
        if (!file_exists($filepath.$filename)) {
6983
            if ($fp = @fopen($filepath.$filename, 'w')) {
6984
                fputs($fp, $content);
6985
                fclose($fp);
6986
6987
                $file_size = filesize($filepath.$filename);
6988
                $save_file_path = $dir.$filename;
6989
6990
                $document_id = add_document(
6991
                    $courseInfo,
6992
                    $save_file_path,
6993
                    'file',
6994
                    $file_size,
6995
                    $tmp_filename,
6996
                    '',
6997
                    0, //readonly
6998
                    true,
6999
                    null,
7000
                    $sessionId,
7001
                    $creatorId
7002
                );
7003
7004
                if ($document_id) {
7005
                    api_item_property_update(
7006
                        $courseInfo,
7007
                        TOOL_DOCUMENT,
7008
                        $document_id,
7009
                        'DocumentAdded',
7010
                        $creatorId,
7011
                        null,
7012
                        null,
7013
                        null,
7014
                        null,
7015
                        $sessionId
7016
                    );
7017
7018
                    $new_comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
7019
                    $new_title = $originalTitle;
7020
7021
                    if ($new_comment || $new_title) {
7022
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7023
                        $ct = '';
7024
                        if ($new_comment) {
7025
                            $ct .= ", comment='".Database::escape_string($new_comment)."'";
7026
                        }
7027
                        if ($new_title) {
7028
                            $ct .= ", title='".Database::escape_string($new_title)."' ";
7029
                        }
7030
7031
                        $sql = "UPDATE ".$tbl_doc." SET ".substr($ct, 1)."
7032
                               WHERE c_id = ".$course_id." AND id = ".$document_id;
7033
                        Database::query($sql);
7034
                    }
7035
                }
7036
7037
                return $document_id;
7038
            }
7039
        }
7040
    }
7041
7042
    /**
7043
     * Edit a document based on $_POST and $_GET parameters 'dir' and 'path'.
7044
     *
7045
     * @param array $_course array
7046
     */
7047
    public function edit_document($_course)
7048
    {
7049
        $course_id = api_get_course_int_id();
7050
        $urlAppend = api_get_configuration_value('url_append');
7051
        // Please, do not modify this dirname formatting.
7052
        $postDir = isset($_POST['dir']) ? $_POST['dir'] : '';
7053
        $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir;
7054
7055
        if (strstr($dir, '..')) {
7056
            $dir = '/';
7057
        }
7058
7059
        if (isset($dir[0]) && $dir[0] == '.') {
7060
            $dir = substr($dir, 1);
7061
        }
7062
7063
        if (isset($dir[0]) && $dir[0] != '/') {
7064
            $dir = '/'.$dir;
7065
        }
7066
7067
        if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
7068
            $dir .= '/';
7069
        }
7070
7071
        $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'.$dir;
7072
        if (!is_dir($filepath)) {
7073
            $filepath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/';
7074
        }
7075
7076
        $table_doc = Database::get_course_table(TABLE_DOCUMENT);
7077
7078
        if (isset($_POST['path']) && !empty($_POST['path'])) {
7079
            $document_id = (int) $_POST['path'];
7080
            $documentInfo = DocumentManager::get_document_data_by_id($document_id, api_get_course_id(), false, null, true);
7081
            if (empty($documentInfo)) {
7082
                // Try with iid
7083
                $table = Database::get_course_table(TABLE_DOCUMENT);
7084
                $sql = "SELECT id, path FROM $table
7085
                        WHERE c_id = $course_id AND iid = $document_id AND path NOT LIKE '%_DELETED_%' ";
7086
                $res_doc = Database::query($sql);
7087
                $row = Database::fetch_array($res_doc);
7088
                if ($row) {
7089
                    $document_id = $row['id'];
7090
                    $documentPath = $row['path'];
7091
                }
7092
            } else {
7093
                $documentPath = $documentInfo['path'];
7094
            }
7095
7096
            $content = stripslashes($_POST['content_lp']);
7097
            $file = $filepath.$documentPath;
7098
7099
            if (!file_exists($file)) {
7100
                return false;
7101
            }
7102
7103
            if ($fp = @fopen($file, 'w')) {
7104
                $content = str_replace(
7105
                    api_get_path(WEB_COURSE_PATH),
7106
                    $urlAppend.api_get_path(REL_COURSE_PATH),
7107
                    $content
7108
                );
7109
                // Change the path of mp3 to absolute.
7110
                // The first regexp deals with :// urls.
7111
                $content = preg_replace(
7112
                    "|(flashvars=\"file=)([^:/]+)/|",
7113
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/',
7114
                    $content
7115
                );
7116
                // The second regexp deals with audio/ urls.
7117
                $content = preg_replace(
7118
                    "|(flashvars=\"file=)([^:/]+)/|",
7119
                    "$1".api_get_path(REL_COURSE_PATH).$_course['path'].'/document/$2/',
7120
                    $content
7121
                );
7122
                fputs($fp, $content);
7123
                fclose($fp);
7124
7125
                $sql = "UPDATE $table_doc SET
7126
                            title='".Database::escape_string($_POST['title'])."'
7127
                        WHERE c_id = $course_id AND id = ".$document_id;
7128
                Database::query($sql);
7129
            }
7130
        }
7131
    }
7132
7133
    /**
7134
     * Displays the selected item, with a panel for manipulating the item.
7135
     *
7136
     * @param int    $item_id
7137
     * @param string $msg
7138
     * @param bool   $show_actions
7139
     *
7140
     * @return string
7141
     */
7142
    public function display_item($item_id, $msg = null, $show_actions = true)
7143
    {
7144
        $course_id = api_get_course_int_id();
7145
        $return = '';
7146
        if (is_numeric($item_id)) {
7147
            $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7148
            $sql = "SELECT lp.* FROM $tbl_lp_item as lp
7149
                    WHERE lp.iid = ".intval($item_id);
7150
            $result = Database::query($sql);
7151
            while ($row = Database::fetch_array($result, 'ASSOC')) {
7152
                $_SESSION['parent_item_id'] = $row['item_type'] == 'dir' ? $item_id : 0;
7153
7154
                // Prevents wrong parent selection for document, see Bug#1251.
7155
                if ($row['item_type'] != 'dir') {
7156
                    $_SESSION['parent_item_id'] = $row['parent_item_id'];
7157
                }
7158
7159
                if ($show_actions) {
7160
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7161
                }
7162
                $return .= '<div style="padding:10px;">';
7163
7164
                if ($msg != '') {
7165
                    $return .= $msg;
7166
                }
7167
7168
                $return .= '<h3>'.$row['title'].'</h3>';
7169
7170
                switch ($row['item_type']) {
7171
                    case TOOL_THREAD:
7172
                        $link = $this->rl_get_resource_link_for_learnpath(
7173
                            $course_id,
7174
                            $row['lp_id'],
7175
                            $item_id,
7176
                            0
7177
                        );
7178
                        $return .= Display::url(
7179
                            get_lang('GoToThread'),
7180
                            $link,
7181
                            ['class' => 'btn btn-primary']
7182
                        );
7183
                        break;
7184
                    case TOOL_FORUM:
7185
                        $return .= Display::url(
7186
                            get_lang('GoToForum'),
7187
                            api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$row['path'],
7188
                            ['class' => 'btn btn-primary']
7189
                        );
7190
                        break;
7191
                    case TOOL_QUIZ:
7192
                        if (!empty($row['path'])) {
7193
                            $exercise = new Exercise();
7194
                            $exercise->read($row['path']);
7195
                            $return .= $exercise->description.'<br />';
7196
                            $return .= Display::url(
7197
                                get_lang('GoToExercise'),
7198
                                api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq().'&exerciseId='.$exercise->id,
7199
                                ['class' => 'btn btn-primary']
7200
                            );
7201
                        }
7202
                        break;
7203
                    case TOOL_LP_FINAL_ITEM:
7204
                        $return .= $this->getSavedFinalItem();
7205
                        break;
7206
                    case TOOL_DOCUMENT:
7207
                    case TOOL_READOUT_TEXT:
7208
                        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7209
                        $sql_doc = "SELECT path FROM $tbl_doc
7210
                                    WHERE c_id = $course_id AND iid = ".intval($row['path']);
7211
                        $result = Database::query($sql_doc);
7212
                        $path_file = Database::result($result, 0, 0);
7213
                        $path_parts = pathinfo($path_file);
7214
                        // TODO: Correct the following naive comparisons.
7215
                        if (in_array($path_parts['extension'], [
7216
                            'html',
7217
                            'txt',
7218
                            'png',
7219
                            'jpg',
7220
                            'JPG',
7221
                            'jpeg',
7222
                            'JPEG',
7223
                            'gif',
7224
                            'swf',
7225
                            'pdf',
7226
                            'htm',
7227
                        ])) {
7228
                            $return .= $this->display_document($row['path'], true, true);
7229
                        }
7230
                        break;
7231
                    case TOOL_HOTPOTATOES:
7232
                        $return .= $this->display_document($row['path'], false, true);
7233
                        break;
7234
                }
7235
                $return .= '</div>';
7236
            }
7237
        }
7238
7239
        return $return;
7240
    }
7241
7242
    /**
7243
     * Shows the needed forms for editing a specific item.
7244
     *
7245
     * @param int $item_id
7246
     *
7247
     * @throws Exception
7248
     * @throws HTML_QuickForm_Error
7249
     *
7250
     * @return string
7251
     */
7252
    public function display_edit_item($item_id)
7253
    {
7254
        $course_id = api_get_course_int_id();
7255
        $return = '';
7256
        $item_id = (int) $item_id;
7257
7258
        if (empty($item_id)) {
7259
            return '';
7260
        }
7261
7262
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
7263
        $sql = "SELECT * FROM $tbl_lp_item
7264
                WHERE iid = ".$item_id;
7265
        $res = Database::query($sql);
7266
        $row = Database::fetch_array($res);
7267
        switch ($row['item_type']) {
7268
            case 'dir':
7269
            case 'asset':
7270
            case 'sco':
7271
                if (isset($_GET['view']) && $_GET['view'] == 'build') {
7272
                    $return .= $this->display_manipulate($item_id, $row['item_type']);
7273
                    $return .= $this->display_item_form(
7274
                        $row['item_type'],
7275
                        get_lang('EditCurrentChapter').' :',
7276
                        'edit',
7277
                        $item_id,
7278
                        $row
7279
                    );
7280
                } else {
7281
                    $return .= $this->display_item_form(
7282
                        $row['item_type'],
7283
                        get_lang('EditCurrentChapter').' :',
7284
                        'edit_item',
7285
                        $item_id,
7286
                        $row
7287
                    );
7288
                }
7289
                break;
7290
            case TOOL_DOCUMENT:
7291
            case TOOL_READOUT_TEXT:
7292
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7293
                $sql = "SELECT lp.*, doc.path as dir
7294
                        FROM $tbl_lp_item as lp
7295
                        LEFT JOIN $tbl_doc as doc
7296
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7297
                        WHERE
7298
                            doc.c_id = $course_id AND
7299
                            lp.iid = ".$item_id;
7300
                $res_step = Database::query($sql);
7301
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7302
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7303
7304
                if ($row['item_type'] === TOOL_DOCUMENT) {
7305
                    $return .= $this->display_document_form('edit', $item_id, $row_step);
7306
                }
7307
7308
                if ($row['item_type'] === TOOL_READOUT_TEXT) {
7309
                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
7310
                }
7311
                break;
7312
            case TOOL_LINK:
7313
                $linkId = (int) $row['path'];
7314
                if (!empty($linkId)) {
7315
                    $table = Database::get_course_table(TABLE_LINK);
7316
                    $sql = 'SELECT url FROM '.$table.'
7317
                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
7318
                    $res_link = Database::query($sql);
7319
                    $row_link = Database::fetch_array($res_link);
7320
                    if (empty($row_link)) {
7321
                        // Try with id
7322
                        $sql = 'SELECT url FROM '.$table.'
7323
                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
7324
                        $res_link = Database::query($sql);
7325
                        $row_link = Database::fetch_array($res_link);
7326
                    }
7327
7328
                    if (is_array($row_link)) {
7329
                        $row['url'] = $row_link['url'];
7330
                    }
7331
                }
7332
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7333
                $return .= $this->display_link_form('edit', $item_id, $row);
7334
                break;
7335
            case TOOL_LP_FINAL_ITEM:
7336
                Session::write('finalItem', true);
7337
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
7338
                $sql = "SELECT lp.*, doc.path as dir
7339
                        FROM $tbl_lp_item as lp
7340
                        LEFT JOIN $tbl_doc as doc
7341
                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
7342
                        WHERE
7343
                            doc.c_id = $course_id AND
7344
                            lp.iid = ".$item_id;
7345
                $res_step = Database::query($sql);
7346
                $row_step = Database::fetch_array($res_step, 'ASSOC');
7347
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7348
                $return .= $this->display_document_form('edit', $item_id, $row_step);
7349
                break;
7350
            case TOOL_QUIZ:
7351
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7352
                $return .= $this->display_quiz_form('edit', $item_id, $row);
7353
                break;
7354
            case TOOL_HOTPOTATOES:
7355
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7356
                $return .= $this->display_hotpotatoes_form('edit', $item_id, $row);
7357
                break;
7358
            case TOOL_STUDENTPUBLICATION:
7359
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7360
                $return .= $this->display_student_publication_form('edit', $item_id, $row);
7361
                break;
7362
            case TOOL_FORUM:
7363
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7364
                $return .= $this->display_forum_form('edit', $item_id, $row);
7365
                break;
7366
            case TOOL_THREAD:
7367
                $return .= $this->display_manipulate($item_id, $row['item_type']);
7368
                $return .= $this->display_thread_form('edit', $item_id, $row);
7369
                break;
7370
        }
7371
7372
        return $return;
7373
    }
7374
7375
    /**
7376
     * Function that displays a list with al the resources that
7377
     * could be added to the learning path.
7378
     *
7379
     * @throws Exception
7380
     * @throws HTML_QuickForm_Error
7381
     *
7382
     * @return bool
7383
     */
7384
    public function display_resources()
7385
    {
7386
        $course_code = api_get_course_id();
7387
7388
        // Get all the docs.
7389
        $documents = $this->get_documents(true);
7390
7391
        // Get all the exercises.
7392
        $exercises = $this->get_exercises();
7393
7394
        // Get all the links.
7395
        $links = $this->get_links();
7396
7397
        // Get all the student publications.
7398
        $works = $this->get_student_publications();
7399
7400
        // Get all the forums.
7401
        $forums = $this->get_forums(null, $course_code);
7402
7403
        // Get the final item form (see BT#11048) .
7404
        $finish = $this->getFinalItemForm();
7405
7406
        $headers = [
7407
            Display::return_icon('folder_document.png', get_lang('Documents'), [], ICON_SIZE_BIG),
7408
            Display::return_icon('quiz.png', get_lang('Quiz'), [], ICON_SIZE_BIG),
7409
            Display::return_icon('links.png', get_lang('Links'), [], ICON_SIZE_BIG),
7410
            Display::return_icon('works.png', get_lang('Works'), [], ICON_SIZE_BIG),
7411
            Display::return_icon('forum.png', get_lang('Forums'), [], ICON_SIZE_BIG),
7412
            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), [], ICON_SIZE_BIG),
7413
            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
7414
        ];
7415
7416
        echo Display::return_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'), 'normal');
7417
        $dir = $this->display_item_form('dir', get_lang('EnterDataNewChapter'), 'add_item');
7418
7419
        $selected = isset($_REQUEST['lp_build_selected']) ? (int) $_REQUEST['lp_build_selected'] : 0;
7420
7421
        echo Display::tabs(
7422
            $headers,
7423
            [
7424
                $documents,
7425
                $exercises,
7426
                $links,
7427
                $works,
7428
                $forums,
7429
                $dir,
7430
                $finish,
7431
            ],
7432
            'resource_tab',
7433
            [],
7434
            [],
7435
            $selected
7436
        );
7437
7438
        return true;
7439
    }
7440
7441
    /**
7442
     * Returns the extension of a document.
7443
     *
7444
     * @param string $filename
7445
     *
7446
     * @return string Extension (part after the last dot)
7447
     */
7448
    public function get_extension($filename)
7449
    {
7450
        $explode = explode('.', $filename);
7451
7452
        return $explode[count($explode) - 1];
7453
    }
7454
7455
    /**
7456
     * Displays a document by id.
7457
     *
7458
     * @param int  $id
7459
     * @param bool $show_title
7460
     * @param bool $iframe
7461
     * @param bool $edit_link
7462
     *
7463
     * @return string
7464
     */
7465
    public function display_document($id, $show_title = false, $iframe = true, $edit_link = false)
7466
    {
7467
        $_course = api_get_course_info();
7468
        $course_id = api_get_course_int_id();
7469
        $id = (int) $id;
7470
        $return = '';
7471
        $table = Database::get_course_table(TABLE_DOCUMENT);
7472
        $sql_doc = "SELECT * FROM $table
7473
                    WHERE c_id = $course_id AND iid = $id";
7474
        $res_doc = Database::query($sql_doc);
7475
        $row_doc = Database::fetch_array($res_doc);
7476
7477
        // TODO: Add a path filter.
7478
        if ($iframe) {
7479
            $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>';
7480
        } else {
7481
            $return .= file_get_contents(api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/'.$row_doc['path']);
7482
        }
7483
7484
        return $return;
7485
    }
7486
7487
    /**
7488
     * Return HTML form to add/edit a quiz.
7489
     *
7490
     * @param string $action     Action (add/edit)
7491
     * @param int    $id         Item ID if already exists
7492
     * @param mixed  $extra_info Extra information (quiz ID if integer)
7493
     *
7494
     * @throws Exception
7495
     *
7496
     * @return string HTML form
7497
     */
7498
    public function display_quiz_form($action = 'add', $id = 0, $extra_info = '')
7499
    {
7500
        $course_id = api_get_course_int_id();
7501
        $id = (int) $id;
7502
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
7503
7504
        if ($id != 0 && is_array($extra_info)) {
7505
            $item_title = $extra_info['title'];
7506
            $item_description = $extra_info['description'];
7507
        } elseif (is_numeric($extra_info)) {
7508
            $sql = "SELECT title, description
7509
                    FROM $tbl_quiz
7510
                    WHERE c_id = $course_id AND iid = ".$extra_info;
7511
7512
            $result = Database::query($sql);
7513
            $row = Database::fetch_array($result);
7514
            $item_title = $row['title'];
7515
            $item_description = $row['description'];
7516
        } else {
7517
            $item_title = '';
7518
            $item_description = '';
7519
        }
7520
        $item_title = Security::remove_XSS($item_title);
7521
        $item_description = Security::remove_XSS($item_description);
7522
7523
        $parent = 0;
7524
        if ($id != 0 && is_array($extra_info)) {
7525
            $parent = $extra_info['parent_item_id'];
7526
        }
7527
7528
        $arrLP = $this->getItemsForForm();
7529
        $this->tree_array($arrLP);
7530
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7531
        unset($this->arrMenu);
7532
7533
        $form = new FormValidator(
7534
            'quiz_form',
7535
            'POST',
7536
            $this->getCurrentBuildingModeURL()
7537
        );
7538
        $defaults = [];
7539
7540
        if ($action === 'add') {
7541
            $legend = get_lang('CreateTheExercise');
7542
        } elseif ($action === 'move') {
7543
            $legend = get_lang('MoveTheCurrentExercise');
7544
        } else {
7545
            $legend = get_lang('EditCurrentExecice');
7546
        }
7547
7548
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7549
            $legend .= Display::return_message(get_lang('Warning').' ! '.get_lang('WarningEditingDocument'));
7550
        }
7551
7552
        $form->addHeader($legend);
7553
7554
        if ($action != 'move') {
7555
            $this->setItemTitle($form);
7556
            $defaults['title'] = $item_title;
7557
        }
7558
7559
        // Select for Parent item, root or chapter
7560
        $selectParent = $form->addSelect(
7561
            'parent',
7562
            get_lang('Parent'),
7563
            [],
7564
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
7565
        );
7566
        $selectParent->addOption($this->name, 0);
7567
7568
        $arrHide = [
7569
            $id,
7570
        ];
7571
        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...
7572
            if ($action != 'add') {
7573
                if (
7574
                    ($arrLP[$i]['item_type'] == 'dir') &&
7575
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7576
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7577
                ) {
7578
                    $selectParent->addOption(
7579
                        $arrLP[$i]['title'],
7580
                        $arrLP[$i]['id'],
7581
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7582
                    );
7583
7584
                    if ($parent == $arrLP[$i]['id']) {
7585
                        $selectParent->setSelected($arrLP[$i]['id']);
7586
                    }
7587
                } else {
7588
                    $arrHide[] = $arrLP[$i]['id'];
7589
                }
7590
            } else {
7591
                if ($arrLP[$i]['item_type'] == 'dir') {
7592
                    $selectParent->addOption(
7593
                        $arrLP[$i]['title'],
7594
                        $arrLP[$i]['id'],
7595
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7596
                    );
7597
7598
                    if ($parent == $arrLP[$i]['id']) {
7599
                        $selectParent->setSelected($arrLP[$i]['id']);
7600
                    }
7601
                }
7602
            }
7603
        }
7604
7605
        if (is_array($arrLP)) {
7606
            reset($arrLP);
7607
        }
7608
7609
        $selectPrevious = $form->addSelect(
7610
            'previous',
7611
            get_lang('Position'),
7612
            [],
7613
            ['id' => 'previous']
7614
        );
7615
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7616
7617
        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...
7618
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7619
                $arrLP[$i]['id'] != $id
7620
            ) {
7621
                $selectPrevious->addOption(
7622
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7623
                    $arrLP[$i]['id']
7624
                );
7625
7626
                if (is_array($extra_info)) {
7627
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7628
                        $selectPrevious->setSelected($arrLP[$i]['id']);
7629
                    }
7630
                } elseif ($action == 'add') {
7631
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7632
                }
7633
            }
7634
        }
7635
7636
        if ($action != 'move') {
7637
            $arrHide = [];
7638
            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...
7639
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7640
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7641
                }
7642
            }
7643
        }
7644
7645
        if ('edit' === $action) {
7646
            $extraField = new ExtraField('lp_item');
7647
            $extraField->addElements($form, $id);
7648
        }
7649
7650
        if ($action === 'add') {
7651
            $form->addButtonSave(get_lang('AddExercise'), 'submit_button');
7652
        } else {
7653
            $form->addButtonSave(get_lang('EditCurrentExecice'), 'submit_button');
7654
        }
7655
7656
        if ($action === 'move') {
7657
            $form->addHidden('title', $item_title);
7658
            $form->addHidden('description', $item_description);
7659
        }
7660
7661
        if (is_numeric($extra_info)) {
7662
            $form->addHidden('path', $extra_info);
7663
        } elseif (is_array($extra_info)) {
7664
            $form->addHidden('path', $extra_info['path']);
7665
        }
7666
7667
        $form->addHidden('type', TOOL_QUIZ);
7668
        $form->addHidden('post_time', time());
7669
        $form->setDefaults($defaults);
7670
7671
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
7672
    }
7673
7674
    /**
7675
     * Addition of Hotpotatoes tests.
7676
     *
7677
     * @param string $action
7678
     * @param int    $id         Internal ID of the item
7679
     * @param string $extra_info
7680
     *
7681
     * @return string HTML structure to display the hotpotatoes addition formular
7682
     */
7683
    public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '')
7684
    {
7685
        $course_id = api_get_course_int_id();
7686
        $uploadPath = DIR_HOTPOTATOES;
7687
7688
        if ($id != 0 && is_array($extra_info)) {
7689
            $item_title = stripslashes($extra_info['title']);
7690
            $item_description = stripslashes($extra_info['description']);
7691
        } elseif (is_numeric($extra_info)) {
7692
            $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
7693
7694
            $sql = "SELECT * FROM $TBL_DOCUMENT
7695
                    WHERE
7696
                        c_id = $course_id AND
7697
                        path LIKE '".$uploadPath."/%/%htm%' AND
7698
                        iid = ".(int) $extra_info."
7699
                    ORDER BY iid ASC";
7700
7701
            $res_hot = Database::query($sql);
7702
            $row = Database::fetch_array($res_hot);
7703
7704
            $item_title = $row['title'];
7705
            $item_description = $row['description'];
7706
7707
            if (!empty($row['comment'])) {
7708
                $item_title = $row['comment'];
7709
            }
7710
        } else {
7711
            $item_title = '';
7712
            $item_description = '';
7713
        }
7714
7715
        $parent = 0;
7716
        if ($id != 0 && is_array($extra_info)) {
7717
            $parent = $extra_info['parent_item_id'];
7718
        }
7719
7720
        $arrLP = $this->getItemsForForm();
7721
        $legend = '<legend>';
7722
        if ($action == 'add') {
7723
            $legend .= get_lang('CreateTheExercise');
7724
        } elseif ($action == 'move') {
7725
            $legend .= get_lang('MoveTheCurrentExercise');
7726
        } else {
7727
            $legend .= get_lang('EditCurrentExecice');
7728
        }
7729
        if (isset($_GET['edit']) && $_GET['edit'] == 'true') {
7730
            $legend .= Display:: return_message(
7731
                get_lang('Warning').' ! '.get_lang('WarningEditingDocument')
7732
            );
7733
        }
7734
        $legend .= '</legend>';
7735
7736
        $return = '<form method="POST">';
7737
        $return .= $legend;
7738
        $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">';
7739
        $return .= '<tr>';
7740
        $return .= '<td class="label"><label for="idParent">'.get_lang('Parent').' :</label></td>';
7741
        $return .= '<td class="input">';
7742
        $return .= '<select id="idParent" name="parent" onChange="javascript: load_cbo(this.value);" size="1">';
7743
        $return .= '<option class="top" value="0">'.$this->name.'</option>';
7744
        $arrHide = [$id];
7745
7746
        if (count($arrLP) > 0) {
7747
            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...
7748
                if ($action != 'add') {
7749
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7750
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7751
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7752
                    ) {
7753
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7754
                    } else {
7755
                        $arrHide[] = $arrLP[$i]['id'];
7756
                    }
7757
                } else {
7758
                    if ($arrLP[$i]['item_type'] == 'dir') {
7759
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7760
                    }
7761
                }
7762
            }
7763
            reset($arrLP);
7764
        }
7765
7766
        $return .= '</select>';
7767
        $return .= '</td>';
7768
        $return .= '</tr>';
7769
        $return .= '<tr>';
7770
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7771
        $return .= '<td class="input">';
7772
        $return .= '<select id="previous" name="previous" size="1">';
7773
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7774
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]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
7777
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
7778
                    $selected = 'selected="selected" ';
7779
                } elseif ($action == 'add') {
7780
                    $selected = 'selected="selected" ';
7781
                } else {
7782
                    $selected = '';
7783
                }
7784
7785
                $return .= '<option '.$selected.'value="'.$arrLP[$i]['id'].'">'.
7786
                    get_lang('After').' "'.$arrLP[$i]['title'].'"</option>';
7787
            }
7788
        }
7789
7790
        $return .= '</select>';
7791
        $return .= '</td>';
7792
        $return .= '</tr>';
7793
7794
        if ($action != 'move') {
7795
            $return .= '<tr>';
7796
            $return .= '<td class="label"><label for="idTitle">'.get_lang('Title').' :</label></td>';
7797
            $return .= '<td class="input"><input id="idTitle" name="title" type="text" value="'.$item_title.'" /></td>';
7798
            $return .= '</tr>';
7799
            $id_prerequisite = 0;
7800
            if (is_array($arrLP) && count($arrLP) > 0) {
7801
                foreach ($arrLP as $key => $value) {
7802
                    if ($value['id'] == $id) {
7803
                        $id_prerequisite = $value['prerequisite'];
7804
                        break;
7805
                    }
7806
                }
7807
7808
                $arrHide = [];
7809
                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...
7810
                    if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7811
                        $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7812
                    }
7813
                }
7814
            }
7815
        }
7816
7817
        $return .= '<tr>';
7818
        $return .= '<td>&nbsp; </td><td><button class="save" name="submit_button" action="edit" type="submit">'.
7819
            get_lang('SaveHotpotatoes').'</button></td>';
7820
        $return .= '</tr>';
7821
        $return .= '</table>';
7822
7823
        if ($action == 'move') {
7824
            $return .= '<input name="title" type="hidden" value="'.$item_title.'" />';
7825
            $return .= '<input name="description" type="hidden" value="'.$item_description.'" />';
7826
        }
7827
7828
        if (is_numeric($extra_info)) {
7829
            $return .= '<input name="path" type="hidden" value="'.$extra_info.'" />';
7830
        } elseif (is_array($extra_info)) {
7831
            $return .= '<input name="path" type="hidden" value="'.$extra_info['path'].'" />';
7832
        }
7833
        $return .= '<input name="type" type="hidden" value="'.TOOL_HOTPOTATOES.'" />';
7834
        $return .= '<input name="post_time" type="hidden" value="'.time().'" />';
7835
        $return .= '</form>';
7836
7837
        return $return;
7838
    }
7839
7840
    /**
7841
     * Return the form to display the forum edit/add option.
7842
     *
7843
     * @param string $action
7844
     * @param int    $id         ID of the lp_item if already exists
7845
     * @param string $extra_info
7846
     *
7847
     * @throws Exception
7848
     *
7849
     * @return string HTML form
7850
     */
7851
    public function display_forum_form($action = 'add', $id = 0, $extra_info = '')
7852
    {
7853
        $course_id = api_get_course_int_id();
7854
        $tbl_forum = Database::get_course_table(TABLE_FORUM);
7855
7856
        $item_title = '';
7857
        $item_description = '';
7858
7859
        if ($id != 0 && is_array($extra_info)) {
7860
            $item_title = stripslashes($extra_info['title']);
7861
        } elseif (is_numeric($extra_info)) {
7862
            $sql = "SELECT forum_title as title, forum_comment as comment
7863
                    FROM $tbl_forum
7864
                    WHERE c_id = $course_id AND forum_id = ".$extra_info;
7865
7866
            $result = Database::query($sql);
7867
            $row = Database::fetch_array($result);
7868
7869
            $item_title = $row['title'];
7870
            $item_description = $row['comment'];
7871
        }
7872
        $parent = 0;
7873
        if ($id != 0 && is_array($extra_info)) {
7874
            $parent = $extra_info['parent_item_id'];
7875
        }
7876
        $arrLP = $this->getItemsForForm();
7877
        $this->tree_array($arrLP);
7878
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
7879
        unset($this->arrMenu);
7880
7881
        if ($action == 'add') {
7882
            $legend = get_lang('CreateTheForum');
7883
        } elseif ($action == 'move') {
7884
            $legend = get_lang('MoveTheCurrentForum');
7885
        } else {
7886
            $legend = get_lang('EditCurrentForum');
7887
        }
7888
7889
        $form = new FormValidator(
7890
            'forum_form',
7891
            'POST',
7892
            $this->getCurrentBuildingModeURL()
7893
        );
7894
        $defaults = [];
7895
7896
        $form->addHeader($legend);
7897
7898
        if ($action != 'move') {
7899
            $this->setItemTitle($form);
7900
            $defaults['title'] = $item_title;
7901
        }
7902
7903
        $selectParent = $form->addSelect(
7904
            'parent',
7905
            get_lang('Parent'),
7906
            [],
7907
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
7908
        );
7909
        $selectParent->addOption($this->name, 0);
7910
        $arrHide = [
7911
            $id,
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 ($action != 'add') {
7915
                if ($arrLP[$i]['item_type'] == 'dir' &&
7916
                    !in_array($arrLP[$i]['id'], $arrHide) &&
7917
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7918
                ) {
7919
                    $selectParent->addOption(
7920
                        $arrLP[$i]['title'],
7921
                        $arrLP[$i]['id'],
7922
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7923
                    );
7924
7925
                    if ($parent == $arrLP[$i]['id']) {
7926
                        $selectParent->setSelected($arrLP[$i]['id']);
7927
                    }
7928
                } else {
7929
                    $arrHide[] = $arrLP[$i]['id'];
7930
                }
7931
            } else {
7932
                if ($arrLP[$i]['item_type'] == 'dir') {
7933
                    $selectParent->addOption(
7934
                        $arrLP[$i]['title'],
7935
                        $arrLP[$i]['id'],
7936
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
7937
                    );
7938
7939
                    if ($parent == $arrLP[$i]['id']) {
7940
                        $selectParent->setSelected($arrLP[$i]['id']);
7941
                    }
7942
                }
7943
            }
7944
        }
7945
7946
        if (is_array($arrLP)) {
7947
            reset($arrLP);
7948
        }
7949
7950
        $selectPrevious = $form->addSelect(
7951
            'previous',
7952
            get_lang('Position'),
7953
            [],
7954
            ['id' => 'previous', 'class' => 'learnpath_item_form']
7955
        );
7956
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
7957
7958
        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...
7959
            if ($arrLP[$i]['parent_item_id'] == $parent &&
7960
                $arrLP[$i]['id'] != $id
7961
            ) {
7962
                $selectPrevious->addOption(
7963
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
7964
                    $arrLP[$i]['id']
7965
                );
7966
7967
                if (isset($extra_info['previous_item_id']) &&
7968
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']
7969
                ) {
7970
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7971
                } elseif ($action == 'add') {
7972
                    $selectPrevious->setSelected($arrLP[$i]['id']);
7973
                }
7974
            }
7975
        }
7976
7977
        if ($action != 'move') {
7978
            $id_prerequisite = 0;
7979
            if (is_array($arrLP)) {
7980
                foreach ($arrLP as $key => $value) {
7981
                    if ($value['id'] == $id) {
7982
                        $id_prerequisite = $value['prerequisite'];
7983
                        break;
7984
                    }
7985
                }
7986
            }
7987
7988
            $arrHide = [];
7989
            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...
7990
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
7991
                    if (isset($extra_info['previous_item_id']) &&
7992
                        $extra_info['previous_item_id'] == $arrLP[$i]['id']
7993
                    ) {
7994
                        $s_selected_position = $arrLP[$i]['id'];
7995
                    } elseif ($action == 'add') {
7996
                        $s_selected_position = 0;
7997
                    }
7998
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
7999
                }
8000
            }
8001
        }
8002
8003
        if ('edit' === $action) {
8004
            $extraField = new ExtraField('lp_item');
8005
            $extraField->addElements($form, $id);
8006
        }
8007
8008
        if ($action == 'add') {
8009
            $form->addButtonSave(get_lang('AddForumToCourse'), 'submit_button');
8010
        } else {
8011
            $form->addButtonSave(get_lang('EditCurrentForum'), 'submit_button');
8012
        }
8013
8014
        if ($action == 'move') {
8015
            $form->addHidden('title', $item_title);
8016
            $form->addHidden('description', $item_description);
8017
        }
8018
8019
        if (is_numeric($extra_info)) {
8020
            $form->addHidden('path', $extra_info);
8021
        } elseif (is_array($extra_info)) {
8022
            $form->addHidden('path', $extra_info['path']);
8023
        }
8024
        $form->addHidden('type', TOOL_FORUM);
8025
        $form->addHidden('post_time', time());
8026
        $form->setDefaults($defaults);
8027
8028
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
8029
    }
8030
8031
    /**
8032
     * Return HTML form to add/edit forum threads.
8033
     *
8034
     * @param string $action
8035
     * @param int    $id         Item ID if already exists in learning path
8036
     * @param string $extra_info
8037
     *
8038
     * @throws Exception
8039
     *
8040
     * @return string HTML form
8041
     */
8042
    public function display_thread_form($action = 'add', $id = 0, $extra_info = '')
8043
    {
8044
        $course_id = api_get_course_int_id();
8045
        if (empty($course_id)) {
8046
            return null;
8047
        }
8048
        $tbl_forum = Database::get_course_table(TABLE_FORUM_THREAD);
8049
8050
        $item_title = '';
8051
        $item_description = '';
8052
        if ($id != 0 && is_array($extra_info)) {
8053
            $item_title = stripslashes($extra_info['title']);
8054
        } elseif (is_numeric($extra_info)) {
8055
            $sql = "SELECT thread_title as title FROM $tbl_forum
8056
                    WHERE c_id = $course_id AND thread_id = ".$extra_info;
8057
8058
            $result = Database::query($sql);
8059
            $row = Database::fetch_array($result);
8060
8061
            $item_title = $row['title'];
8062
            $item_description = '';
8063
        }
8064
8065
        $parent = 0;
8066
        if ($id != 0 && is_array($extra_info)) {
8067
            $parent = $extra_info['parent_item_id'];
8068
        }
8069
8070
        $arrLP = $this->getItemsForForm();
8071
        $this->tree_array($arrLP);
8072
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8073
        unset($this->arrMenu);
8074
8075
        $form = new FormValidator(
8076
            'thread_form',
8077
            'POST',
8078
            $this->getCurrentBuildingModeURL()
8079
        );
8080
        $defaults = [];
8081
8082
        if ($action == 'add') {
8083
            $legend = get_lang('CreateTheForum');
8084
        } elseif ($action == 'move') {
8085
            $legend = get_lang('MoveTheCurrentForum');
8086
        } else {
8087
            $legend = get_lang('EditCurrentForum');
8088
        }
8089
8090
        $form->addHeader($legend);
8091
        $selectParent = $form->addSelect(
8092
            'parent',
8093
            get_lang('Parent'),
8094
            [],
8095
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);']
8096
        );
8097
        $selectParent->addOption($this->name, 0);
8098
8099
        $arrHide = [
8100
            $id,
8101
        ];
8102
8103
        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...
8104
            if ($action != 'add') {
8105
                if (
8106
                    ($arrLP[$i]['item_type'] == 'dir') &&
8107
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8108
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8109
                ) {
8110
                    $selectParent->addOption(
8111
                        $arrLP[$i]['title'],
8112
                        $arrLP[$i]['id'],
8113
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8114
                    );
8115
8116
                    if ($parent == $arrLP[$i]['id']) {
8117
                        $selectParent->setSelected($arrLP[$i]['id']);
8118
                    }
8119
                } else {
8120
                    $arrHide[] = $arrLP[$i]['id'];
8121
                }
8122
            } else {
8123
                if ($arrLP[$i]['item_type'] == 'dir') {
8124
                    $selectParent->addOption(
8125
                        $arrLP[$i]['title'],
8126
                        $arrLP[$i]['id'],
8127
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
8128
                    );
8129
8130
                    if ($parent == $arrLP[$i]['id']) {
8131
                        $selectParent->setSelected($arrLP[$i]['id']);
8132
                    }
8133
                }
8134
            }
8135
        }
8136
8137
        if ($arrLP != null) {
8138
            reset($arrLP);
8139
        }
8140
8141
        $selectPrevious = $form->addSelect(
8142
            'previous',
8143
            get_lang('Position'),
8144
            [],
8145
            ['id' => 'previous']
8146
        );
8147
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
8148
8149
        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...
8150
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
8151
                $selectPrevious->addOption(
8152
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
8153
                    $arrLP[$i]['id']
8154
                );
8155
8156
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8157
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8158
                } elseif ($action == 'add') {
8159
                    $selectPrevious->setSelected($arrLP[$i]['id']);
8160
                }
8161
            }
8162
        }
8163
8164
        if ($action != 'move') {
8165
            $this->setItemTitle($form);
8166
            $defaults['title'] = $item_title;
8167
8168
            $id_prerequisite = 0;
8169
            if ($arrLP != null) {
8170
                foreach ($arrLP as $key => $value) {
8171
                    if ($value['id'] == $id) {
8172
                        $id_prerequisite = $value['prerequisite'];
8173
                        break;
8174
                    }
8175
                }
8176
            }
8177
8178
            $arrHide = [];
8179
            $s_selected_position = 0;
8180
            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...
8181
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
8182
                    if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
8183
                        $s_selected_position = $arrLP[$i]['id'];
8184
                    } elseif ($action == 'add') {
8185
                        $s_selected_position = 0;
8186
                    }
8187
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8188
                }
8189
            }
8190
8191
            $selectPrerequisites = $form->addSelect(
8192
                'prerequisites',
8193
                get_lang('LearnpathPrerequisites'),
8194
                [],
8195
                ['id' => 'prerequisites']
8196
            );
8197
            $selectPrerequisites->addOption(get_lang('NoPrerequisites'), 0);
8198
8199
            foreach ($arrHide as $key => $value) {
8200
                $selectPrerequisites->addOption($value['value'], $key);
8201
8202
                if ($key == $s_selected_position && $action == 'add') {
8203
                    $selectPrerequisites->setSelected($key);
8204
                } elseif ($key == $id_prerequisite && $action == 'edit') {
8205
                    $selectPrerequisites->setSelected($key);
8206
                }
8207
            }
8208
        }
8209
8210
        if ('edit' === $action) {
8211
            $extraField = new ExtraField('lp_item');
8212
            $extraField->addElements($form, $id);
8213
        }
8214
8215
        $form->addButtonSave(get_lang('Ok'), 'submit_button');
8216
8217
        if ($action == 'move') {
8218
            $form->addHidden('title', $item_title);
8219
            $form->addHidden('description', $item_description);
8220
        }
8221
8222
        if (is_numeric($extra_info)) {
8223
            $form->addHidden('path', $extra_info);
8224
        } elseif (is_array($extra_info)) {
8225
            $form->addHidden('path', $extra_info['path']);
8226
        }
8227
8228
        $form->addHidden('type', TOOL_THREAD);
8229
        $form->addHidden('post_time', time());
8230
        $form->setDefaults($defaults);
8231
8232
        return $form->returnForm();
8233
    }
8234
8235
    /**
8236
     * Return the HTML form to display an item (generally a dir item).
8237
     *
8238
     * @param string $item_type
8239
     * @param string $title
8240
     * @param string $action
8241
     * @param int    $id
8242
     * @param string $extra_info
8243
     *
8244
     * @throws Exception
8245
     * @throws HTML_QuickForm_Error
8246
     *
8247
     * @return string HTML form
8248
     */
8249
    public function display_item_form(
8250
        $item_type,
8251
        $title = '',
8252
        $action = 'add_item',
8253
        $id = 0,
8254
        $extra_info = 'new'
8255
    ) {
8256
        $_course = api_get_course_info();
8257
8258
        global $charset;
8259
8260
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
8261
        $item_title = '';
8262
        $item_description = '';
8263
        $item_path_fck = '';
8264
8265
        $parent = 0;
8266
        $previousId = null;
8267
        if ($id != 0 && is_array($extra_info)) {
8268
            $item_title = $extra_info['title'];
8269
            $item_description = $extra_info['description'];
8270
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8271
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($extra_info['path']);
8272
            $parent = $extra_info['parent_item_id'];
8273
            $previousId = $extra_info['previous_item_id'];
8274
        }
8275
8276
        if ($extra_info instanceof learnpathItem) {
8277
            $item_title = $extra_info->get_title();
8278
            $item_description = $extra_info->get_description();
8279
            $path = $extra_info->get_path();
8280
            $item_path = api_get_path(WEB_COURSE_PATH).$_course['path'].'/scorm/'.$this->path.'/'.stripslashes($path);
8281
            $item_path_fck = '/scorm/'.$this->path.'/'.stripslashes($path);
8282
            $parent = $extra_info->get_parent();
8283
            $previousId = $extra_info->previous;
8284
        }
8285
8286
        $id = (int) $id;
8287
        $sql = "SELECT * FROM $tbl_lp_item
8288
                WHERE
8289
                    lp_id = ".$this->lp_id." AND
8290
                    iid != $id";
8291
8292
        if ($item_type === 'dir') {
8293
            $sql .= " AND parent_item_id = 0";
8294
        }
8295
8296
        $result = Database::query($sql);
8297
        $arrLP = [];
8298
        while ($row = Database::fetch_array($result)) {
8299
            $arrLP[] = [
8300
                'id' => $row['iid'],
8301
                'item_type' => $row['item_type'],
8302
                'title' => $this->cleanItemTitle($row['title']),
8303
                'title_raw' => $row['title'],
8304
                'path' => $row['path'],
8305
                'description' => $row['description'],
8306
                'parent_item_id' => $row['parent_item_id'],
8307
                'previous_item_id' => $row['previous_item_id'],
8308
                'next_item_id' => $row['next_item_id'],
8309
                'max_score' => $row['max_score'],
8310
                'min_score' => $row['min_score'],
8311
                'mastery_score' => $row['mastery_score'],
8312
                'prerequisite' => $row['prerequisite'],
8313
                'display_order' => $row['display_order'],
8314
            ];
8315
        }
8316
8317
        $this->tree_array($arrLP);
8318
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8319
        unset($this->arrMenu);
8320
8321
        $url = api_get_self().'?'.api_get_cidreq().'&action='.$action.'&type='.$item_type.'&lp_id='.$this->lp_id;
8322
8323
        $form = new FormValidator('form_'.$item_type, 'POST', $url);
8324
        $defaults['title'] = api_html_entity_decode(
8325
            $item_title,
8326
            ENT_QUOTES,
8327
            $charset
8328
        );
8329
        $defaults['description'] = $item_description;
8330
8331
        $form->addHeader($title);
8332
        $arrHide[0]['value'] = Security::remove_XSS($this->name);
8333
        $arrHide[0]['padding'] = 20;
8334
        $charset = api_get_system_encoding();
8335
        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...
8336
            if ($action != 'add') {
8337
                if ($arrLP[$i]['item_type'] === 'dir' && !in_array($arrLP[$i]['id'], $arrHide) &&
8338
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8339
                ) {
8340
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8341
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8342
                    if ($parent == $arrLP[$i]['id']) {
8343
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8344
                    }
8345
                }
8346
            } else {
8347
                if ($arrLP[$i]['item_type'] === 'dir') {
8348
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8349
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8350
                    if ($parent == $arrLP[$i]['id']) {
8351
                        $s_selected_parent = $arrHide[$arrLP[$i]['id']];
8352
                    }
8353
                }
8354
            }
8355
        }
8356
8357
        if ($action !== 'move') {
8358
            $this->setItemTitle($form);
8359
        } else {
8360
            $form->addElement('hidden', 'title');
8361
        }
8362
8363
        $parentSelect = $form->addElement(
8364
            'select',
8365
            'parent',
8366
            get_lang('Parent'),
8367
            '',
8368
            [
8369
                'id' => 'idParent',
8370
                'onchange' => 'javascript: load_cbo(this.value);',
8371
            ]
8372
        );
8373
8374
        foreach ($arrHide as $key => $value) {
8375
            $parentSelect->addOption(
8376
                $value['value'],
8377
                $key,
8378
                'style="padding-left:'.$value['padding'].'px;"'
8379
            );
8380
            $lastPosition = $key;
8381
        }
8382
8383
        if (!empty($s_selected_parent)) {
8384
            $parentSelect->setSelected($s_selected_parent);
8385
        }
8386
8387
        if (is_array($arrLP)) {
8388
            reset($arrLP);
8389
        }
8390
8391
        $arrHide = [];
8392
        // POSITION
8393
        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...
8394
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id &&
8395
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
8396
                //this is the same!
8397
                if (isset($previousId) && $previousId == $arrLP[$i]['id']) {
8398
                    $s_selected_position = $arrLP[$i]['id'];
8399
                } elseif ($action === 'add') {
8400
                    $s_selected_position = $arrLP[$i]['id'];
8401
                }
8402
8403
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8404
            }
8405
        }
8406
8407
        $position = $form->addElement(
8408
            'select',
8409
            'previous',
8410
            get_lang('Position'),
8411
            '',
8412
            ['id' => 'previous']
8413
        );
8414
        $padding = isset($value['padding']) ? $value['padding'] : 0;
8415
        $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:'.$padding.'px;"');
8416
8417
        $lastPosition = null;
8418
        foreach ($arrHide as $key => $value) {
8419
            $position->addOption($value['value'], $key, 'style="padding-left:'.$padding.'px;"');
8420
            $lastPosition = $key;
8421
        }
8422
8423
        if (!empty($s_selected_position)) {
8424
            $position->setSelected($s_selected_position);
8425
        }
8426
8427
        // When new chapter add at the end
8428
        if ($action === 'add_item') {
8429
            $position->setSelected($lastPosition);
8430
        }
8431
8432
        if (is_array($arrLP)) {
8433
            reset($arrLP);
8434
        }
8435
8436
        $form->addButtonSave(get_lang('SaveSection'), 'submit_button');
8437
8438
        //fix in order to use the tab
8439
        if ($item_type === 'dir') {
8440
            $form->addElement('hidden', 'type', 'dir');
8441
        }
8442
8443
        $extension = null;
8444
        if (!empty($item_path)) {
8445
            $extension = pathinfo($item_path, PATHINFO_EXTENSION);
8446
        }
8447
8448
        //assets can't be modified
8449
        //$item_type == 'asset' ||
8450
        if (($item_type === 'sco') && ($extension === 'html' || $extension === 'htm')) {
8451
            if ($item_type === 'sco') {
8452
                $form->addElement(
8453
                    'html',
8454
                    '<script>alert("'.get_lang('WarningWhenEditingScorm').'")</script>'
8455
                );
8456
            }
8457
            $renderer = $form->defaultRenderer();
8458
            $renderer->setElementTemplate(
8459
                '<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{label}<br />{element}',
8460
                'content_lp'
8461
            );
8462
8463
            $relative_prefix = '';
8464
            $editor_config = [
8465
                'ToolbarSet' => 'LearningPathDocuments',
8466
                'Width' => '100%',
8467
                'Height' => '500',
8468
                'FullPage' => true,
8469
                'CreateDocumentDir' => $relative_prefix,
8470
                'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/scorm/',
8471
                'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().$item_path_fck,
8472
            ];
8473
8474
            $form->addElement('html_editor', 'content_lp', '', null, $editor_config);
8475
            $content_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().$item_path_fck;
8476
            $defaults['content_lp'] = file_get_contents($content_path);
8477
        }
8478
8479
        if (!empty($id)) {
8480
            $form->addHidden('id', $id);
8481
        }
8482
8483
        $form->addElement('hidden', 'type', $item_type);
8484
        $form->addElement('hidden', 'post_time', time());
8485
        $form->setDefaults($defaults);
8486
8487
        return $form->returnForm();
8488
    }
8489
8490
    /**
8491
     * @return string
8492
     */
8493
    public function getCurrentBuildingModeURL()
8494
    {
8495
        $pathItem = isset($_GET['path_item']) ? (int) $_GET['path_item'] : '';
8496
        $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : '';
8497
        $id = isset($_GET['id']) ? (int) $_GET['id'] : '';
8498
        $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : '';
8499
8500
        $currentUrl = api_get_self().'?'.api_get_cidreq().
8501
            '&action='.$action.'&lp_id='.$this->lp_id.'&path_item='.$pathItem.'&view='.$view.'&id='.$id;
8502
8503
        return $currentUrl;
8504
    }
8505
8506
    /**
8507
     * Returns the form to update or create a document.
8508
     *
8509
     * @param string $action     (add/edit)
8510
     * @param int    $id         ID of the lp_item (if already exists)
8511
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8512
     *
8513
     * @throws Exception
8514
     * @throws HTML_QuickForm_Error
8515
     *
8516
     * @return string HTML form
8517
     */
8518
    public function display_document_form($action = 'add', $id = 0, $extra_info = 'new')
8519
    {
8520
        $course_id = api_get_course_int_id();
8521
        $_course = api_get_course_info();
8522
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8523
8524
        $no_display_edit_textarea = false;
8525
        $item_description = '';
8526
        //If action==edit document
8527
        //We don't display the document form if it's not an editable document (html or txt file)
8528
        if ($action === 'edit') {
8529
            if (is_array($extra_info)) {
8530
                $path_parts = pathinfo($extra_info['dir']);
8531
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8532
                    $no_display_edit_textarea = true;
8533
                }
8534
            }
8535
        }
8536
        $no_display_add = false;
8537
8538
        // If action==add an existing document
8539
        // We don't display the document form if it's not an editable document (html or txt file).
8540
        if ($action === 'add') {
8541
            if (is_numeric($extra_info)) {
8542
                $extra_info = (int) $extra_info;
8543
                $sql_doc = "SELECT path FROM $tbl_doc
8544
                            WHERE c_id = $course_id AND iid = ".$extra_info;
8545
                $result = Database::query($sql_doc);
8546
                $path_file = Database::result($result, 0, 0);
8547
                $path_parts = pathinfo($path_file);
8548
                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
8549
                    $no_display_add = true;
8550
                }
8551
            }
8552
        }
8553
8554
        $item_title = '';
8555
        $item_description = '';
8556
        if ($id != 0 && is_array($extra_info)) {
8557
            $item_title = stripslashes($extra_info['title']);
8558
            $item_description = stripslashes($extra_info['description']);
8559
            if (empty($item_title)) {
8560
                $path_parts = pathinfo($extra_info['path']);
8561
                $item_title = stripslashes($path_parts['filename']);
8562
            }
8563
        } elseif (is_numeric($extra_info)) {
8564
            $sql = "SELECT path, title FROM $tbl_doc
8565
                    WHERE
8566
                        c_id = ".$course_id." AND
8567
                        iid = ".intval($extra_info);
8568
            $result = Database::query($sql);
8569
            $row = Database::fetch_array($result);
8570
            $item_title = $row['title'];
8571
            $item_title = str_replace('_', ' ', $item_title);
8572
            if (empty($item_title)) {
8573
                $path_parts = pathinfo($row['path']);
8574
                $item_title = stripslashes($path_parts['filename']);
8575
            }
8576
        }
8577
8578
        $return = '<legend>';
8579
        $parent = 0;
8580
        if ($id != 0 && is_array($extra_info)) {
8581
            $parent = $extra_info['parent_item_id'];
8582
        }
8583
8584
        $arrLP = $this->getItemsForForm();
8585
        $this->tree_array($arrLP);
8586
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8587
        unset($this->arrMenu);
8588
8589
        if ($action === 'add') {
8590
            $return .= get_lang('CreateTheDocument');
8591
        } elseif ($action === 'move') {
8592
            $return .= get_lang('MoveTheCurrentDocument');
8593
        } else {
8594
            $return .= get_lang('EditTheCurrentDocument');
8595
        }
8596
        $return .= '</legend>';
8597
8598
        if (isset($_GET['edit']) && $_GET['edit'] === 'true') {
8599
            $return .= Display::return_message(
8600
                '<strong>'.get_lang('Warning').' !</strong><br />'.get_lang('WarningEditingDocument'),
8601
                false
8602
            );
8603
        }
8604
        $form = new FormValidator(
8605
            'form',
8606
            'POST',
8607
            $this->getCurrentBuildingModeURL(),
8608
            '',
8609
            ['enctype' => 'multipart/form-data']
8610
        );
8611
        $defaults['title'] = Security::remove_XSS($item_title);
8612
        if (empty($item_title)) {
8613
            $defaults['title'] = Security::remove_XSS($item_title);
8614
        }
8615
        $defaults['description'] = $item_description;
8616
        $form->addElement('html', $return);
8617
8618
        if ($action !== 'move') {
8619
            $data = $this->generate_lp_folder($_course);
8620
            if ($action !== 'edit') {
8621
                $folders = DocumentManager::get_all_document_folders(
8622
                    $_course,
8623
                    0,
8624
                    true
8625
                );
8626
                DocumentManager::build_directory_selector(
8627
                    $folders,
8628
                    '',
8629
                    [],
8630
                    true,
8631
                    $form,
8632
                    'directory_parent_id'
8633
                );
8634
            }
8635
8636
            if (isset($data['id'])) {
8637
                $defaults['directory_parent_id'] = $data['id'];
8638
            }
8639
            $this->setItemTitle($form);
8640
        }
8641
8642
        $arrHide[0]['value'] = $this->name;
8643
        $arrHide[0]['padding'] = 20;
8644
8645
        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...
8646
            if ($action !== 'add') {
8647
                if ($arrLP[$i]['item_type'] === 'dir' &&
8648
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8649
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8650
                ) {
8651
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8652
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8653
                }
8654
            } else {
8655
                if ($arrLP[$i]['item_type'] == 'dir') {
8656
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8657
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8658
                }
8659
            }
8660
        }
8661
8662
        $parentSelect = $form->addSelect(
8663
            'parent',
8664
            get_lang('Parent'),
8665
            [],
8666
            [
8667
                'id' => 'idParent',
8668
                'onchange' => 'javascript: load_cbo(this.value);',
8669
            ]
8670
        );
8671
8672
        $my_count = 0;
8673
        foreach ($arrHide as $key => $value) {
8674
            if ($my_count != 0) {
8675
                // The LP name is also the first section and is not in the same charset like the other sections.
8676
                $value['value'] = Security::remove_XSS($value['value']);
8677
                $parentSelect->addOption(
8678
                    $value['value'],
8679
                    $key,
8680
                    'style="padding-left:'.$value['padding'].'px;"'
8681
                );
8682
            } else {
8683
                $value['value'] = Security::remove_XSS($value['value']);
8684
                $parentSelect->addOption(
8685
                    $value['value'],
8686
                    $key,
8687
                    'style="padding-left:'.$value['padding'].'px;"'
8688
                );
8689
            }
8690
            $my_count++;
8691
        }
8692
8693
        if (!empty($id)) {
8694
            $parentSelect->setSelected($parent);
8695
        } else {
8696
            $parent_item_id = Session::read('parent_item_id', 0);
8697
            $parentSelect->setSelected($parent_item_id);
8698
        }
8699
8700
        if (is_array($arrLP)) {
8701
            reset($arrLP);
8702
        }
8703
8704
        $arrHide = [];
8705
        // POSITION
8706
        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...
8707
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) ||
8708
                $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM
8709
            ) {
8710
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
8711
            }
8712
        }
8713
8714
        $selectedPosition = isset($extra_info['previous_item_id']) ? $extra_info['previous_item_id'] : 0;
8715
8716
        $position = $form->addSelect(
8717
            'previous',
8718
            get_lang('Position'),
8719
            [],
8720
            ['id' => 'previous']
8721
        );
8722
8723
        $position->addOption(get_lang('FirstPosition'), 0);
8724
        foreach ($arrHide as $key => $value) {
8725
            $padding = isset($value['padding']) ? $value['padding'] : 20;
8726
            $position->addOption(
8727
                $value['value'],
8728
                $key,
8729
                'style="padding-left:'.$padding.'px;"'
8730
            );
8731
        }
8732
8733
        $position->setSelected($selectedPosition);
8734
8735
        if (is_array($arrLP)) {
8736
            reset($arrLP);
8737
        }
8738
8739
        if ('edit' === $action) {
8740
            $extraField = new ExtraField('lp_item');
8741
            $extraField->addElements($form, $id);
8742
        }
8743
8744
        if ($action !== 'move') {
8745
            $arrHide = [];
8746
            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...
8747
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] !== 'dir' &&
8748
                    $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
8749
                ) {
8750
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8751
                }
8752
            }
8753
8754
            if (!$no_display_add) {
8755
                $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
8756
                $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
8757
                if ($extra_info === 'new' || $item_type == TOOL_DOCUMENT ||
8758
                    $item_type == TOOL_LP_FINAL_ITEM || $edit === 'true'
8759
                ) {
8760
                    if (isset($_POST['content'])) {
8761
                        $content = stripslashes($_POST['content']);
8762
                    } elseif (is_array($extra_info)) {
8763
                        //If it's an html document or a text file
8764
                        if (!$no_display_edit_textarea) {
8765
                            $content = $this->display_document(
8766
                                $extra_info['path'],
8767
                                false,
8768
                                false
8769
                            );
8770
                        }
8771
                    } elseif (is_numeric($extra_info)) {
8772
                        $content = $this->display_document(
8773
                            $extra_info,
8774
                            false,
8775
                            false
8776
                        );
8777
                    } else {
8778
                        $content = '';
8779
                    }
8780
8781
                    if (!$no_display_edit_textarea) {
8782
                        // We need to calculate here some specific settings for the online editor.
8783
                        // The calculated settings work for documents in the Documents tool
8784
                        // (on the root or in subfolders).
8785
                        // For documents in native scorm packages it is unclear whether the
8786
                        // online editor should be activated or not.
8787
8788
                        // A new document, it is in the root of the repository.
8789
                        $relative_path = '';
8790
                        $relative_prefix = '';
8791
                        if (is_array($extra_info) && $extra_info != 'new') {
8792
                            // The document already exists. Whe have to determine its relative path towards the repository root.
8793
                            $relative_path = explode('/', $extra_info['dir']);
8794
                            $cnt = count($relative_path) - 2;
8795
                            if ($cnt < 0) {
8796
                                $cnt = 0;
8797
                            }
8798
                            $relative_prefix = str_repeat('../', $cnt);
8799
                            $relative_path = array_slice($relative_path, 1, $cnt);
8800
                            $relative_path = implode('/', $relative_path);
8801
                            if (strlen($relative_path) > 0) {
8802
                                $relative_path = $relative_path.'/';
8803
                            }
8804
                        } else {
8805
                            $result = $this->generate_lp_folder($_course);
8806
                            $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
8807
                            $relative_prefix = '../../';
8808
                        }
8809
8810
                        $editor_config = [
8811
                            'ToolbarSet' => 'LearningPathDocuments',
8812
                            'Width' => '100%',
8813
                            'Height' => '500',
8814
                            'FullPage' => true,
8815
                            'CreateDocumentDir' => $relative_prefix,
8816
                            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
8817
                            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
8818
                        ];
8819
8820
                        if ($_GET['action'] === 'add_item') {
8821
                            $class = 'add';
8822
                            $text = get_lang('LPCreateDocument');
8823
                        } else {
8824
                            if ($_GET['action'] === 'edit_item') {
8825
                                $class = 'save';
8826
                                $text = get_lang('SaveDocument');
8827
                            }
8828
                        }
8829
8830
                        $form->addButtonSave($text, 'submit_button');
8831
                        $renderer = $form->defaultRenderer();
8832
                        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp');
8833
                        $form->addElement('html', '<div class="editor-lp">');
8834
                        $form->addHtmlEditor('content_lp', null, null, true, $editor_config, true);
8835
                        $form->addElement('html', '</div>');
8836
                        $defaults['content_lp'] = $content;
8837
                    }
8838
                } elseif (is_numeric($extra_info)) {
8839
                    $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8840
8841
                    $return = $this->display_document($extra_info, true, true, true);
8842
                    $form->addElement('html', $return);
8843
                }
8844
            }
8845
        }
8846
        if (isset($extra_info['item_type']) &&
8847
            $extra_info['item_type'] == TOOL_LP_FINAL_ITEM
8848
        ) {
8849
            $parentSelect->freeze();
8850
            $position->freeze();
8851
        }
8852
8853
        if ($action === 'move') {
8854
            $form->addElement('hidden', 'title', $item_title);
8855
            $form->addElement('hidden', 'description', $item_description);
8856
        }
8857
        if (is_numeric($extra_info)) {
8858
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8859
            $form->addElement('hidden', 'path', $extra_info);
8860
        } elseif (is_array($extra_info)) {
8861
            $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
8862
            $form->addElement('hidden', 'path', $extra_info['path']);
8863
        }
8864
        $form->addElement('hidden', 'type', TOOL_DOCUMENT);
8865
        $form->addElement('hidden', 'post_time', time());
8866
        $form->setDefaults($defaults);
8867
8868
        return $form->returnForm();
8869
    }
8870
8871
    /**
8872
     * Returns the form to update or create a read-out text.
8873
     *
8874
     * @param string $action     "add" or "edit"
8875
     * @param int    $id         ID of the lp_item (if already exists)
8876
     * @param mixed  $extra_info Integer if document ID, string if info ('new')
8877
     *
8878
     * @throws Exception
8879
     * @throws HTML_QuickForm_Error
8880
     *
8881
     * @return string HTML form
8882
     */
8883
    public function displayFrmReadOutText($action = 'add', $id = 0, $extra_info = 'new')
8884
    {
8885
        $course_id = api_get_course_int_id();
8886
        $_course = api_get_course_info();
8887
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
8888
8889
        $no_display_edit_textarea = false;
8890
        //If action==edit document
8891
        //We don't display the document form if it's not an editable document (html or txt file)
8892
        if ($action == 'edit') {
8893
            if (is_array($extra_info)) {
8894
                $path_parts = pathinfo($extra_info['dir']);
8895
                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
8896
                    $no_display_edit_textarea = true;
8897
                }
8898
            }
8899
        }
8900
        $no_display_add = false;
8901
8902
        $item_title = '';
8903
        $item_description = '';
8904
        if ($id != 0 && is_array($extra_info)) {
8905
            $item_title = stripslashes($extra_info['title']);
8906
            $item_description = stripslashes($extra_info['description']);
8907
            $item_terms = stripslashes($extra_info['terms']);
8908
            if (empty($item_title)) {
8909
                $path_parts = pathinfo($extra_info['path']);
8910
                $item_title = stripslashes($path_parts['filename']);
8911
            }
8912
        } elseif (is_numeric($extra_info)) {
8913
            $sql = "SELECT path, title FROM $tbl_doc WHERE c_id = ".$course_id." AND iid = ".intval($extra_info);
8914
            $result = Database::query($sql);
8915
            $row = Database::fetch_array($result);
8916
            $item_title = $row['title'];
8917
            $item_title = str_replace('_', ' ', $item_title);
8918
            if (empty($item_title)) {
8919
                $path_parts = pathinfo($row['path']);
8920
                $item_title = stripslashes($path_parts['filename']);
8921
            }
8922
        }
8923
8924
        $parent = 0;
8925
        if ($id != 0 && is_array($extra_info)) {
8926
            $parent = $extra_info['parent_item_id'];
8927
        }
8928
8929
        $arrLP = $this->getItemsForForm();
8930
        $this->tree_array($arrLP);
8931
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
8932
        unset($this->arrMenu);
8933
8934
        if ($action === 'add') {
8935
            $formHeader = get_lang('CreateTheDocument');
8936
        } else {
8937
            $formHeader = get_lang('EditTheCurrentDocument');
8938
        }
8939
8940
        if ('edit' === $action) {
8941
            $urlAudioIcon = Display::url(
8942
                Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY),
8943
                api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'&'
8944
                    .http_build_query(['view' => 'build', 'id' => $id, 'action' => 'add_audio'])
8945
            );
8946
        } else {
8947
            $urlAudioIcon = Display::return_icon('audio.png', get_lang('CreateReadOutText'), [], ICON_SIZE_TINY);
8948
        }
8949
8950
        $form = new FormValidator(
8951
            'frm_add_reading',
8952
            'POST',
8953
            $this->getCurrentBuildingModeURL(),
8954
            '',
8955
            ['enctype' => 'multipart/form-data']
8956
        );
8957
        $form->addHeader($formHeader);
8958
        $form->addHtml(
8959
            Display::return_message(
8960
                sprintf(get_lang('FrmReadOutTextIntro'), $urlAudioIcon),
8961
                'normal',
8962
                false
8963
            )
8964
        );
8965
        $defaults['title'] = !empty($item_title) ? Security::remove_XSS($item_title) : '';
8966
        $defaults['description'] = $item_description;
8967
8968
        $data = $this->generate_lp_folder($_course);
8969
8970
        if ($action != 'edit') {
8971
            $folders = DocumentManager::get_all_document_folders($_course, 0, true);
8972
            DocumentManager::build_directory_selector(
8973
                $folders,
8974
                '',
8975
                [],
8976
                true,
8977
                $form,
8978
                'directory_parent_id'
8979
            );
8980
        }
8981
8982
        if (isset($data['id'])) {
8983
            $defaults['directory_parent_id'] = $data['id'];
8984
        }
8985
        $this->setItemTitle($form);
8986
8987
        $arrHide[0]['value'] = $this->name;
8988
        $arrHide[0]['padding'] = 20;
8989
8990
        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...
8991
            if ($action != 'add') {
8992
                if ($arrLP[$i]['item_type'] == 'dir' &&
8993
                    !in_array($arrLP[$i]['id'], $arrHide) &&
8994
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
8995
                ) {
8996
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
8997
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
8998
                }
8999
            } else {
9000
                if ($arrLP[$i]['item_type'] == 'dir') {
9001
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9002
                    $arrHide[$arrLP[$i]['id']]['padding'] = 20 + $arrLP[$i]['depth'] * 20;
9003
                }
9004
            }
9005
        }
9006
9007
        $parent_select = $form->addSelect(
9008
            'parent',
9009
            get_lang('Parent'),
9010
            [],
9011
            ['onchange' => "javascript: load_cbo(this.value, 'frm_add_reading_previous');"]
9012
        );
9013
9014
        $my_count = 0;
9015
        foreach ($arrHide as $key => $value) {
9016
            if ($my_count != 0) {
9017
                // The LP name is also the first section and is not in the same charset like the other sections.
9018
                $value['value'] = Security::remove_XSS($value['value']);
9019
                $parent_select->addOption(
9020
                    $value['value'],
9021
                    $key,
9022
                    'style="padding-left:'.$value['padding'].'px;"'
9023
                );
9024
            } else {
9025
                $value['value'] = Security::remove_XSS($value['value']);
9026
                $parent_select->addOption(
9027
                    $value['value'],
9028
                    $key,
9029
                    'style="padding-left:'.$value['padding'].'px;"'
9030
                );
9031
            }
9032
            $my_count++;
9033
        }
9034
9035
        if (!empty($id)) {
9036
            $parent_select->setSelected($parent);
9037
        } else {
9038
            $parent_item_id = Session::read('parent_item_id', 0);
9039
            $parent_select->setSelected($parent_item_id);
9040
        }
9041
9042
        if (is_array($arrLP)) {
9043
            reset($arrLP);
9044
        }
9045
9046
        $arrHide = [];
9047
        $s_selected_position = null;
9048
9049
        // POSITION
9050
        $lastPosition = null;
9051
9052
        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...
9053
            if (($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) &&
9054
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9055
            ) {
9056
                if ((isset($extra_info['previous_item_id']) &&
9057
                    $extra_info['previous_item_id'] == $arrLP[$i]['id']) || $action == 'add'
9058
                ) {
9059
                    $s_selected_position = $arrLP[$i]['id'];
9060
                }
9061
                $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After').' "'.$arrLP[$i]['title'].'"';
9062
            }
9063
            $lastPosition = $arrLP[$i]['id'];
9064
        }
9065
9066
        if (empty($s_selected_position)) {
9067
            $s_selected_position = $lastPosition;
9068
        }
9069
9070
        $position = $form->addSelect(
9071
            'previous',
9072
            get_lang('Position'),
9073
            []
9074
        );
9075
        $position->addOption(get_lang('FirstPosition'), 0);
9076
9077
        foreach ($arrHide as $key => $value) {
9078
            $padding = isset($value['padding']) ? $value['padding'] : 20;
9079
            $position->addOption(
9080
                $value['value'],
9081
                $key,
9082
                'style="padding-left:'.$padding.'px;"'
9083
            );
9084
        }
9085
        $position->setSelected($s_selected_position);
9086
9087
        if (is_array($arrLP)) {
9088
            reset($arrLP);
9089
        }
9090
9091
        if ('edit' === $action) {
9092
            $extraField = new ExtraField('lp_item');
9093
            $extraField->addElements($form, $id);
9094
        }
9095
9096
        $arrHide = [];
9097
9098
        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...
9099
            if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir' &&
9100
                $arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM
9101
            ) {
9102
                $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9103
            }
9104
        }
9105
9106
        if (!$no_display_add) {
9107
            $item_type = isset($extra_info['item_type']) ? $extra_info['item_type'] : null;
9108
            $edit = isset($_GET['edit']) ? $_GET['edit'] : null;
9109
9110
            if ($extra_info == 'new' || $item_type == TOOL_READOUT_TEXT || $edit == 'true') {
9111
                if (!$no_display_edit_textarea) {
9112
                    $content = '';
9113
9114
                    if (isset($_POST['content'])) {
9115
                        $content = stripslashes($_POST['content']);
9116
                    } elseif (is_array($extra_info)) {
9117
                        $content = $this->display_document($extra_info['path'], false, false);
9118
                    } elseif (is_numeric($extra_info)) {
9119
                        $content = $this->display_document($extra_info, false, false);
9120
                    }
9121
9122
                    // A new document, it is in the root of the repository.
9123
                    if (is_array($extra_info) && $extra_info != 'new') {
9124
                    } else {
9125
                        $this->generate_lp_folder($_course);
9126
                    }
9127
9128
                    if ($_GET['action'] == 'add_item') {
9129
                        $text = get_lang('LPCreateDocument');
9130
                    } else {
9131
                        $text = get_lang('SaveDocument');
9132
                    }
9133
9134
                    $form->addTextarea('content_lp', get_lang('Content'), ['rows' => 20]);
9135
                    $form
9136
                        ->defaultRenderer()
9137
                        ->setElementTemplate($form->getDefaultElementTemplate(), 'content_lp');
9138
                    $form->addButtonSave($text, 'submit_button');
9139
                    $defaults['content_lp'] = $content;
9140
                }
9141
            } elseif (is_numeric($extra_info)) {
9142
                $form->addButtonSave(get_lang('SaveDocument'), 'submit_button');
9143
9144
                $return = $this->display_document($extra_info, true, true, true);
9145
                $form->addElement('html', $return);
9146
            }
9147
        }
9148
9149
        if (is_numeric($extra_info)) {
9150
            $form->addElement('hidden', 'path', $extra_info);
9151
        } elseif (is_array($extra_info)) {
9152
            $form->addElement('hidden', 'path', $extra_info['path']);
9153
        }
9154
9155
        $form->addElement('hidden', 'type', TOOL_READOUT_TEXT);
9156
        $form->addElement('hidden', 'post_time', time());
9157
        $form->setDefaults($defaults);
9158
9159
        return $form->returnForm();
9160
    }
9161
9162
    /**
9163
     * @param array  $courseInfo
9164
     * @param string $content
9165
     * @param string $title
9166
     * @param int    $parentId
9167
     *
9168
     * @throws \Doctrine\ORM\ORMException
9169
     * @throws \Doctrine\ORM\OptimisticLockException
9170
     * @throws \Doctrine\ORM\TransactionRequiredException
9171
     *
9172
     * @return int
9173
     */
9174
    public function createReadOutText($courseInfo, $content = '', $title = '', $parentId = 0)
9175
    {
9176
        $creatorId = api_get_user_id();
9177
        $sessionId = api_get_session_id();
9178
9179
        // Generates folder
9180
        $result = $this->generate_lp_folder($courseInfo);
9181
        $dir = $result['dir'];
9182
9183
        if (empty($parentId) || $parentId == '/') {
9184
            $postDir = isset($_POST['dir']) ? $_POST['dir'] : $dir;
9185
            $dir = isset($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
9186
9187
            if ($parentId === '/') {
9188
                $dir = '/';
9189
            }
9190
9191
            // Please, do not modify this dirname formatting.
9192
            if (strstr($dir, '..')) {
9193
                $dir = '/';
9194
            }
9195
9196
            if (!empty($dir[0]) && $dir[0] == '.') {
9197
                $dir = substr($dir, 1);
9198
            }
9199
            if (!empty($dir[0]) && $dir[0] != '/') {
9200
                $dir = '/'.$dir;
9201
            }
9202
            if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
9203
                $dir .= '/';
9204
            }
9205
        } else {
9206
            $parentInfo = DocumentManager::get_document_data_by_id(
9207
                $parentId,
9208
                $courseInfo['code']
9209
            );
9210
            if (!empty($parentInfo)) {
9211
                $dir = $parentInfo['path'].'/';
9212
            }
9213
        }
9214
9215
        $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9216
9217
        if (!is_dir($filepath)) {
9218
            $dir = '/';
9219
            $filepath = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$dir;
9220
        }
9221
9222
        $originalTitle = !empty($title) ? $title : $_POST['title'];
9223
9224
        if (!empty($title)) {
9225
            $title = api_replace_dangerous_char(stripslashes($title));
9226
        } else {
9227
            $title = api_replace_dangerous_char(stripslashes($_POST['title']));
9228
        }
9229
9230
        $title = disable_dangerous_file($title);
9231
        $filename = $title;
9232
        $content = !empty($content) ? $content : $_POST['content_lp'];
9233
        $tmpFileName = $filename;
9234
9235
        $i = 0;
9236
        while (file_exists($filepath.$tmpFileName.'.html')) {
9237
            $tmpFileName = $filename.'_'.++$i;
9238
        }
9239
9240
        $filename = $tmpFileName.'.html';
9241
        $content = stripslashes($content);
9242
9243
        if (file_exists($filepath.$filename)) {
9244
            return 0;
9245
        }
9246
9247
        $putContent = file_put_contents($filepath.$filename, $content);
9248
9249
        if ($putContent === false) {
9250
            return 0;
9251
        }
9252
9253
        $fileSize = filesize($filepath.$filename);
9254
        $saveFilePath = $dir.$filename;
9255
9256
        $documentId = add_document(
9257
            $courseInfo,
9258
            $saveFilePath,
9259
            'file',
9260
            $fileSize,
9261
            $tmpFileName,
9262
            '',
9263
            0, //readonly
9264
            true,
9265
            null,
9266
            $sessionId,
9267
            $creatorId
9268
        );
9269
9270
        if (!$documentId) {
9271
            return 0;
9272
        }
9273
9274
        api_item_property_update(
9275
            $courseInfo,
9276
            TOOL_DOCUMENT,
9277
            $documentId,
9278
            'DocumentAdded',
9279
            $creatorId,
9280
            null,
9281
            null,
9282
            null,
9283
            null,
9284
            $sessionId
9285
        );
9286
9287
        $newComment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
9288
        $newTitle = $originalTitle;
9289
9290
        if ($newComment || $newTitle) {
9291
            $em = Database::getManager();
9292
9293
            /** @var CDocument $doc */
9294
            $doc = $em->find('ChamiloCourseBundle:CDocument', $documentId);
9295
9296
            if ($newComment) {
9297
                $doc->setComment($newComment);
9298
            }
9299
9300
            if ($newTitle) {
9301
                $doc->setTitle($newTitle);
9302
            }
9303
9304
            $em->persist($doc);
9305
            $em->flush();
9306
        }
9307
9308
        return $documentId;
9309
    }
9310
9311
    /**
9312
     * Return HTML form to add/edit a link item.
9313
     *
9314
     * @param string $action     (add/edit)
9315
     * @param int    $id         Item ID if exists
9316
     * @param mixed  $extra_info
9317
     *
9318
     * @throws Exception
9319
     * @throws HTML_QuickForm_Error
9320
     *
9321
     * @return string HTML form
9322
     */
9323
    public function display_link_form($action = 'add', $id = 0, $extra_info = '')
9324
    {
9325
        $course_id = api_get_course_int_id();
9326
        $tbl_link = Database::get_course_table(TABLE_LINK);
9327
9328
        $item_title = '';
9329
        $item_description = '';
9330
        $item_url = '';
9331
9332
        if ($id != 0 && is_array($extra_info)) {
9333
            $item_title = stripslashes($extra_info['title']);
9334
            $item_description = stripslashes($extra_info['description']);
9335
            $item_url = stripslashes($extra_info['url']);
9336
        } elseif (is_numeric($extra_info)) {
9337
            $extra_info = (int) $extra_info;
9338
            $sql = "SELECT title, description, url
9339
                    FROM $tbl_link
9340
                    WHERE c_id = $course_id AND iid = $extra_info";
9341
            $result = Database::query($sql);
9342
            $row = Database::fetch_array($result);
9343
            $item_title = $row['title'];
9344
            $item_description = $row['description'];
9345
            $item_url = $row['url'];
9346
        }
9347
9348
        $form = new FormValidator(
9349
            'edit_link',
9350
            'POST',
9351
            $this->getCurrentBuildingModeURL()
9352
        );
9353
        $defaults = [];
9354
        $parent = 0;
9355
        if ($id != 0 && is_array($extra_info)) {
9356
            $parent = $extra_info['parent_item_id'];
9357
        }
9358
9359
        $arrLP = $this->getItemsForForm();
9360
9361
        $this->tree_array($arrLP);
9362
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9363
        unset($this->arrMenu);
9364
9365
        if ($action == 'add') {
9366
            $legend = get_lang('CreateTheLink');
9367
        } elseif ($action == 'move') {
9368
            $legend = get_lang('MoveCurrentLink');
9369
        } else {
9370
            $legend = get_lang('EditCurrentLink');
9371
        }
9372
9373
        $form->addHeader($legend);
9374
9375
        if ($action != 'move') {
9376
            $this->setItemTitle($form);
9377
            $defaults['title'] = $item_title;
9378
        }
9379
9380
        $selectParent = $form->addSelect(
9381
            'parent',
9382
            get_lang('Parent'),
9383
            [],
9384
            ['id' => 'idParent', 'onchange' => 'load_cbo(this.value);', 'class' => 'learnpath_item_form']
9385
        );
9386
        $selectParent->addOption($this->name, 0);
9387
        $arrHide = [
9388
            $id,
9389
        ];
9390
9391
        $parent_item_id = Session::read('parent_item_id', 0);
9392
9393
        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...
9394
            if ($action != 'add') {
9395
                if (
9396
                    ($arrLP[$i]['item_type'] == 'dir') &&
9397
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9398
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9399
                ) {
9400
                    $selectParent->addOption(
9401
                        $arrLP[$i]['title'],
9402
                        $arrLP[$i]['id'],
9403
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px;']
9404
                    );
9405
9406
                    if ($parent == $arrLP[$i]['id']) {
9407
                        $selectParent->setSelected($arrLP[$i]['id']);
9408
                    }
9409
                } else {
9410
                    $arrHide[] = $arrLP[$i]['id'];
9411
                }
9412
            } else {
9413
                if ($arrLP[$i]['item_type'] == 'dir') {
9414
                    $selectParent->addOption(
9415
                        $arrLP[$i]['title'],
9416
                        $arrLP[$i]['id'],
9417
                        ['style' => 'padding-left: '.(20 + $arrLP[$i]['depth'] * 20).'px']
9418
                    );
9419
9420
                    if ($parent_item_id == $arrLP[$i]['id']) {
9421
                        $selectParent->setSelected($arrLP[$i]['id']);
9422
                    }
9423
                }
9424
            }
9425
        }
9426
9427
        if (is_array($arrLP)) {
9428
            reset($arrLP);
9429
        }
9430
9431
        $selectPrevious = $form->addSelect(
9432
            'previous',
9433
            get_lang('Position'),
9434
            [],
9435
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9436
        );
9437
        $selectPrevious->addOption(get_lang('FirstPosition'), 0);
9438
9439
        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...
9440
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9441
                $selectPrevious->addOption(
9442
                    $arrLP[$i]['title'],
9443
                    $arrLP[$i]['id']
9444
                );
9445
9446
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9447
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9448
                } elseif ($action == 'add') {
9449
                    $selectPrevious->setSelected($arrLP[$i]['id']);
9450
                }
9451
            }
9452
        }
9453
9454
        if ($action != 'move') {
9455
            $urlAttributes = ['class' => 'learnpath_item_form'];
9456
9457
            if (is_numeric($extra_info)) {
9458
                $urlAttributes['disabled'] = 'disabled';
9459
            }
9460
9461
            $form->addElement('url', 'url', get_lang('Url'), $urlAttributes);
9462
            $defaults['url'] = $item_url;
9463
            $arrHide = [];
9464
            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...
9465
                if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dir') {
9466
                    $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title'];
9467
                }
9468
            }
9469
        }
9470
9471
        if ('edit' === $action) {
9472
            $extraField = new ExtraField('lp_item');
9473
            $extraField->addElements($form, $id);
9474
        }
9475
9476
        if ($action == 'add') {
9477
            $form->addButtonSave(get_lang('AddLinkToCourse'), 'submit_button');
9478
        } else {
9479
            $form->addButtonSave(get_lang('EditCurrentLink'), 'submit_button');
9480
        }
9481
9482
        if ($action == 'move') {
9483
            $form->addHidden('title', $item_title);
9484
            $form->addHidden('description', $item_description);
9485
        }
9486
9487
        if (is_numeric($extra_info)) {
9488
            $form->addHidden('path', $extra_info);
9489
        } elseif (is_array($extra_info)) {
9490
            $form->addHidden('path', $extra_info['path']);
9491
        }
9492
        $form->addHidden('type', TOOL_LINK);
9493
        $form->addHidden('post_time', time());
9494
        $form->setDefaults($defaults);
9495
9496
        return '<div class="sectioncomment">'.$form->returnForm().'</div>';
9497
    }
9498
9499
    /**
9500
     * Return HTML form to add/edit a student publication (work).
9501
     *
9502
     * @param string $action
9503
     * @param int    $id         Item ID if already exists
9504
     * @param string $extra_info
9505
     *
9506
     * @throws Exception
9507
     *
9508
     * @return string HTML form
9509
     */
9510
    public function display_student_publication_form(
9511
        $action = 'add',
9512
        $id = 0,
9513
        $extra_info = ''
9514
    ) {
9515
        $course_id = api_get_course_int_id();
9516
        $tbl_publication = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
9517
9518
        $item_title = get_lang('Student_publication');
9519
        if ($id != 0 && is_array($extra_info)) {
9520
            $item_title = stripslashes($extra_info['title']);
9521
            $item_description = stripslashes($extra_info['description']);
9522
        } elseif (is_numeric($extra_info)) {
9523
            $extra_info = (int) $extra_info;
9524
            $sql = "SELECT title, description
9525
                    FROM $tbl_publication
9526
                    WHERE c_id = $course_id AND id = ".$extra_info;
9527
9528
            $result = Database::query($sql);
9529
            $row = Database::fetch_array($result);
9530
            if ($row) {
9531
                $item_title = $row['title'];
9532
            }
9533
        }
9534
9535
        $parent = 0;
9536
        if ($id != 0 && is_array($extra_info)) {
9537
            $parent = $extra_info['parent_item_id'];
9538
        }
9539
9540
        $arrLP = $this->getItemsForForm();
9541
9542
        $this->tree_array($arrLP);
9543
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9544
        unset($this->arrMenu);
9545
9546
        $form = new FormValidator('frm_student_publication', 'post', '#');
9547
9548
        if ($action == 'add') {
9549
            $form->addHeader(get_lang('Student_publication'));
9550
        } elseif ($action == 'move') {
9551
            $form->addHeader(get_lang('MoveCurrentStudentPublication'));
9552
        } else {
9553
            $form->addHeader(get_lang('EditCurrentStudentPublication'));
9554
        }
9555
9556
        if ($action != 'move') {
9557
            $this->setItemTitle($form);
9558
        }
9559
9560
        $parentSelect = $form->addSelect(
9561
            'parent',
9562
            get_lang('Parent'),
9563
            ['0' => $this->name],
9564
            [
9565
                'onchange' => 'javascript: load_cbo(this.value);',
9566
                'class' => 'learnpath_item_form',
9567
                'id' => 'idParent',
9568
            ]
9569
        );
9570
9571
        $arrHide = [$id];
9572
        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...
9573
            if ($action != 'add') {
9574
                if (
9575
                    ($arrLP[$i]['item_type'] == 'dir') &&
9576
                    !in_array($arrLP[$i]['id'], $arrHide) &&
9577
                    !in_array($arrLP[$i]['parent_item_id'], $arrHide)
9578
                ) {
9579
                    $parentSelect->addOption(
9580
                        $arrLP[$i]['title'],
9581
                        $arrLP[$i]['id'],
9582
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9583
                    );
9584
9585
                    if ($parent == $arrLP[$i]['id']) {
9586
                        $parentSelect->setSelected($arrLP[$i]['id']);
9587
                    }
9588
                } else {
9589
                    $arrHide[] = $arrLP[$i]['id'];
9590
                }
9591
            } else {
9592
                if ($arrLP[$i]['item_type'] == 'dir') {
9593
                    $parentSelect->addOption(
9594
                        $arrLP[$i]['title'],
9595
                        $arrLP[$i]['id'],
9596
                        ['style' => 'padding-left: '.(($arrLP[$i]['depth'] * 10) + 20).'px;']
9597
                    );
9598
9599
                    if ($parent == $arrLP[$i]['id']) {
9600
                        $parentSelect->setSelected($arrLP[$i]['id']);
9601
                    }
9602
                }
9603
            }
9604
        }
9605
9606
        if (is_array($arrLP)) {
9607
            reset($arrLP);
9608
        }
9609
9610
        $previousSelect = $form->addSelect(
9611
            'previous',
9612
            get_lang('Position'),
9613
            ['0' => get_lang('FirstPosition')],
9614
            ['id' => 'previous', 'class' => 'learnpath_item_form']
9615
        );
9616
9617
        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...
9618
            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
9619
                $previousSelect->addOption(
9620
                    get_lang('After').' "'.$arrLP[$i]['title'].'"',
9621
                    $arrLP[$i]['id']
9622
                );
9623
9624
                if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) {
9625
                    $previousSelect->setSelected($arrLP[$i]['id']);
9626
                } elseif ($action == 'add') {
9627
                    $previousSelect->setSelected($arrLP[$i]['id']);
9628
                }
9629
            }
9630
        }
9631
9632
        if ('edit' === $action) {
9633
            $extraField = new ExtraField('lp_item');
9634
            $extraField->addElements($form, $id);
9635
        }
9636
9637
        if ($action == 'add') {
9638
            $form->addButtonCreate(get_lang('AddAssignmentToCourse'), 'submit_button');
9639
        } else {
9640
            $form->addButtonCreate(get_lang('EditCurrentStudentPublication'), 'submit_button');
9641
        }
9642
9643
        if ($action == 'move') {
9644
            $form->addHidden('title', $item_title);
9645
            $form->addHidden('description', $item_description);
9646
        }
9647
9648
        if (is_numeric($extra_info)) {
9649
            $form->addHidden('path', $extra_info);
9650
        } elseif (is_array($extra_info)) {
9651
            $form->addHidden('path', $extra_info['path']);
9652
        }
9653
9654
        $form->addHidden('type', TOOL_STUDENTPUBLICATION);
9655
        $form->addHidden('post_time', time());
9656
        $form->setDefaults(['title' => $item_title]);
9657
9658
        $return = '<div class="sectioncomment">';
9659
        $return .= $form->returnForm();
9660
        $return .= '</div>';
9661
9662
        return $return;
9663
    }
9664
9665
    /**
9666
     * Displays the menu for manipulating a step.
9667
     *
9668
     * @param int    $item_id
9669
     * @param string $item_type
9670
     *
9671
     * @return string
9672
     */
9673
    public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT)
9674
    {
9675
        $_course = api_get_course_info();
9676
        $course_code = api_get_course_id();
9677
        $item_id = (int) $item_id;
9678
9679
        $return = '<div class="actions">';
9680
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9681
        $sql = "SELECT * FROM $tbl_lp_item
9682
                WHERE iid = ".$item_id;
9683
        $result = Database::query($sql);
9684
        $row = Database::fetch_assoc($result);
9685
9686
        $audio_player = null;
9687
        // We display an audio player if needed.
9688
        if (!empty($row['audio'])) {
9689
            $audio = learnpathItem::fixAudio($row['audio']);
9690
            $webAudioPath = '../..'.api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$audio;
9691
            $audio_player .= '<div class="lp_mediaplayer" id="container">
9692
                            <audio src="'.$webAudioPath.'" controls>
9693
                            </div><br />';
9694
        }
9695
9696
        $url = api_get_self().'?'.api_get_cidreq().'&view=build&id='.$item_id.'&lp_id='.$this->lp_id;
9697
9698
        if ($item_type != TOOL_LP_FINAL_ITEM) {
9699
            $return .= Display::url(
9700
                Display::return_icon(
9701
                    'edit.png',
9702
                    get_lang('Edit'),
9703
                    [],
9704
                    ICON_SIZE_SMALL
9705
                ),
9706
                $url.'&action=edit_item&path_item='.$row['path']
9707
            );
9708
9709
            $return .= Display::url(
9710
                Display::return_icon(
9711
                    'move.png',
9712
                    get_lang('Move'),
9713
                    [],
9714
                    ICON_SIZE_SMALL
9715
                ),
9716
                $url.'&action=move_item'
9717
            );
9718
        }
9719
9720
        // Commented for now as prerequisites cannot be added to chapters.
9721
        if ($item_type != 'dir') {
9722
            $return .= Display::url(
9723
                Display::return_icon(
9724
                    'accept.png',
9725
                    get_lang('LearnpathPrerequisites'),
9726
                    [],
9727
                    ICON_SIZE_SMALL
9728
                ),
9729
                $url.'&action=edit_item_prereq'
9730
            );
9731
        }
9732
        $return .= Display::url(
9733
            Display::return_icon(
9734
                'delete.png',
9735
                get_lang('Delete'),
9736
                [],
9737
                ICON_SIZE_SMALL
9738
            ),
9739
            $url.'&action=delete_item'
9740
        );
9741
9742
        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
9743
            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
9744
            if (empty($documentData)) {
9745
                // Try with iid
9746
                $table = Database::get_course_table(TABLE_DOCUMENT);
9747
                $sql = "SELECT path FROM $table
9748
                        WHERE
9749
                              c_id = ".api_get_course_int_id()." AND
9750
                              iid = ".$row['path']." AND
9751
                              path NOT LIKE '%_DELETED_%'";
9752
                $result = Database::query($sql);
9753
                $documentData = Database::fetch_array($result);
9754
                if ($documentData) {
9755
                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
9756
                }
9757
            }
9758
            if (isset($documentData['absolute_path_from_document'])) {
9759
                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
9760
            }
9761
        }
9762
9763
        $return .= '</div>';
9764
9765
        if (!empty($audio_player)) {
9766
            $return .= $audio_player;
9767
        }
9768
9769
        return $return;
9770
    }
9771
9772
    /**
9773
     * Creates the javascript needed for filling up the checkboxes without page reload.
9774
     *
9775
     * @return string
9776
     */
9777
    public function get_js_dropdown_array()
9778
    {
9779
        $course_id = api_get_course_int_id();
9780
        $return = 'var child_name = new Array();'."\n";
9781
        $return .= 'var child_value = new Array();'."\n\n";
9782
        $return .= 'child_name[0] = new Array();'."\n";
9783
        $return .= 'child_value[0] = new Array();'."\n\n";
9784
9785
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9786
        $sql = "SELECT * FROM ".$tbl_lp_item."
9787
                WHERE
9788
                    c_id = $course_id AND
9789
                    lp_id = ".$this->lp_id." AND
9790
                    parent_item_id = 0
9791
                ORDER BY display_order ASC";
9792
        $res_zero = Database::query($sql);
9793
        $i = 0;
9794
9795
        $list = $this->getItemsForForm(true);
9796
9797
        foreach ($list as $row_zero) {
9798
            if ($row_zero['item_type'] !== TOOL_LP_FINAL_ITEM) {
9799
                if ($row_zero['item_type'] == TOOL_QUIZ) {
9800
                    $row_zero['title'] = Exercise::get_formated_title_variable($row_zero['title']);
9801
                }
9802
                $js_var = json_encode(get_lang('After').' '.$row_zero['title']);
9803
                $return .= 'child_name[0]['.$i.'] = '.$js_var.' ;'."\n";
9804
                $return .= 'child_value[0]['.$i++.'] = "'.$row_zero['iid'].'";'."\n";
9805
            }
9806
        }
9807
9808
        $return .= "\n";
9809
        $sql = "SELECT * FROM $tbl_lp_item
9810
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9811
        $res = Database::query($sql);
9812
        while ($row = Database::fetch_array($res)) {
9813
            $sql_parent = "SELECT * FROM ".$tbl_lp_item."
9814
                           WHERE
9815
                                c_id = ".$course_id." AND
9816
                                parent_item_id = ".$row['iid']."
9817
                           ORDER BY display_order ASC";
9818
            $res_parent = Database::query($sql_parent);
9819
            $i = 0;
9820
            $return .= 'child_name['.$row['iid'].'] = new Array();'."\n";
9821
            $return .= 'child_value['.$row['iid'].'] = new Array();'."\n\n";
9822
9823
            while ($row_parent = Database::fetch_array($res_parent)) {
9824
                $js_var = json_encode(get_lang('After').' '.$this->cleanItemTitle($row_parent['title']));
9825
                $return .= 'child_name['.$row['iid'].']['.$i.'] =   '.$js_var.' ;'."\n";
9826
                $return .= 'child_value['.$row['iid'].']['.$i++.'] = "'.$row_parent['iid'].'";'."\n";
9827
            }
9828
            $return .= "\n";
9829
        }
9830
9831
        $return .= "
9832
            function load_cbo(id) {
9833
                if (!id) {
9834
                    return false;
9835
                }
9836
9837
                var cbo = document.getElementById('previous');
9838
                for(var i = cbo.length - 1; i > 0; i--) {
9839
                    cbo.options[i] = null;
9840
                }
9841
9842
                var k=0;
9843
                for(var i = 1; i <= child_name[id].length; i++){
9844
                    var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
9845
                    option.style.paddingLeft = '40px';
9846
                    cbo.options[i] = option;
9847
                    k = i;
9848
                }
9849
9850
                cbo.options[k].selected = true;
9851
                $('#previous').selectpicker('refresh');
9852
            }";
9853
9854
        return $return;
9855
    }
9856
9857
    /**
9858
     * Display the form to allow moving an item.
9859
     *
9860
     * @param learnpathItem $item Item ID
9861
     *
9862
     * @throws Exception
9863
     * @throws HTML_QuickForm_Error
9864
     *
9865
     * @return string HTML form
9866
     */
9867
    public function display_move_item($item)
9868
    {
9869
        $return = '';
9870
        if ($item) {
9871
            $item_id = $item->getIid();
9872
            $type = $item->get_type();
9873
9874
            switch ($type) {
9875
                case 'dir':
9876
                case 'asset':
9877
                    $return .= $this->display_manipulate($item_id, $type);
9878
                    $return .= $this->display_item_form(
9879
                        $type,
9880
                        get_lang('MoveCurrentChapter'),
9881
                        'move',
9882
                        $item_id,
9883
                        $item
9884
                    );
9885
                    break;
9886
                case TOOL_DOCUMENT:
9887
                    $return .= $this->display_manipulate($item_id, $type);
9888
                    $return .= $this->display_document_form('move', $item_id, $item);
9889
                    break;
9890
                case TOOL_LINK:
9891
                    $return .= $this->display_manipulate($item_id, $type);
9892
                    $return .= $this->display_link_form('move', $item_id, $item);
9893
                    break;
9894
                case TOOL_HOTPOTATOES:
9895
                    $return .= $this->display_manipulate($item_id, $type);
9896
                    $return .= $this->display_link_form('move', $item_id, $item);
9897
                    break;
9898
                case TOOL_QUIZ:
9899
                    $return .= $this->display_manipulate($item_id, $type);
9900
                    $return .= $this->display_quiz_form('move', $item_id, $item);
9901
                    break;
9902
                case TOOL_STUDENTPUBLICATION:
9903
                    $return .= $this->display_manipulate($item_id, $type);
9904
                    $return .= $this->display_student_publication_form('move', $item_id, $item);
9905
                    break;
9906
                case TOOL_FORUM:
9907
                    $return .= $this->display_manipulate($item_id, $type);
9908
                    $return .= $this->display_forum_form('move', $item_id, $item);
9909
                    break;
9910
                case TOOL_THREAD:
9911
                    $return .= $this->display_manipulate($item_id, $type);
9912
                    $return .= $this->display_forum_form('move', $item_id, $item);
9913
                    break;
9914
            }
9915
        }
9916
9917
        return $return;
9918
    }
9919
9920
    /**
9921
     * Return HTML form to allow prerequisites selection.
9922
     *
9923
     * @todo use FormValidator
9924
     *
9925
     * @param int Item ID
9926
     *
9927
     * @return string HTML form
9928
     */
9929
    public function display_item_prerequisites_form($item_id = 0)
9930
    {
9931
        $course_id = api_get_course_int_id();
9932
        $item_id = (int) $item_id;
9933
9934
        if (empty($item_id)) {
9935
            return '';
9936
        }
9937
9938
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
9939
9940
        /* Current prerequisite */
9941
        $sql = "SELECT * FROM $tbl_lp_item
9942
                WHERE iid = $item_id";
9943
        $result = Database::query($sql);
9944
        $row = Database::fetch_array($result);
9945
        $prerequisiteId = $row['prerequisite'];
9946
9947
        $return = '<legend>';
9948
        $return .= get_lang('AddEditPrerequisites');
9949
        $return .= '</legend>';
9950
        $return .= '<form method="POST">';
9951
        $return .= '<div class="table-responsive">';
9952
        $return .= '<table class="table table-hover">';
9953
        $return .= '<thead>';
9954
        $return .= '<tr>';
9955
        $return .= '<th>'.get_lang('LearnpathPrerequisites').'</th>';
9956
        $return .= '<th width="140">'.get_lang('Minimum').'</th>';
9957
        $return .= '<th width="140">'.get_lang('Maximum').'</th>';
9958
        $return .= '</tr>';
9959
        $return .= '</thead>';
9960
9961
        // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146
9962
        $return .= '<tbody>';
9963
        $return .= '<tr>';
9964
        $return .= '<td colspan="3">';
9965
        $return .= '<div class="radio learnpath"><label for="idNone">';
9966
        $return .= '<input checked="checked" id="idNone" name="prerequisites" type="radio" />';
9967
        $return .= get_lang('None').'</label>';
9968
        $return .= '</div>';
9969
        $return .= '</tr>';
9970
9971
        $sql = "SELECT * FROM $tbl_lp_item
9972
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
9973
        $result = Database::query($sql);
9974
9975
        $selectedMinScore = [];
9976
        $selectedMaxScore = [];
9977
        $masteryScore = [];
9978
        while ($row = Database::fetch_array($result)) {
9979
            if ($row['iid'] == $item_id) {
9980
                $selectedMinScore[$row['prerequisite']] = $row['prerequisite_min_score'];
9981
                $selectedMaxScore[$row['prerequisite']] = $row['prerequisite_max_score'];
9982
            }
9983
            $masteryScore[$row['iid']] = $row['mastery_score'];
9984
        }
9985
9986
        $arrLP = $this->getItemsForForm();
9987
        $this->tree_array($arrLP);
9988
        $arrLP = isset($this->arrMenu) ? $this->arrMenu : [];
9989
        unset($this->arrMenu);
9990
9991
        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...
9992
            $item = $arrLP[$i];
9993
9994
            if ($item['id'] == $item_id) {
9995
                break;
9996
            }
9997
9998
            $selectedMaxScoreValue = isset($selectedMaxScore[$item['id']]) ? $selectedMaxScore[$item['id']] : $item['max_score'];
9999
            $selectedMinScoreValue = isset($selectedMinScore[$item['id']]) ? $selectedMinScore[$item['id']] : 0;
10000
            $masteryScoreAsMinValue = isset($masteryScore[$item['id']]) ? $masteryScore[$item['id']] : 0;
10001
10002
            $return .= '<tr>';
10003
            $return .= '<td '.(($item['item_type'] != TOOL_QUIZ && $item['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '').'>';
10004
            $return .= '<div style="margin-left:'.($item['depth'] * 20).'px;" class="radio learnpath">';
10005
            $return .= '<label for="id'.$item['id'].'">';
10006
10007
            $checked = '';
10008
            if (null !== $prerequisiteId) {
10009
                $checked = in_array($prerequisiteId, [$item['id'], $item['ref']]) ? ' checked="checked" ' : '';
10010
            }
10011
10012
            $disabled = $item['item_type'] === 'dir' ? ' disabled="disabled" ' : '';
10013
10014
            $return .= '<input '.$checked.' '.$disabled.' id="id'.$item['id'].'" name="prerequisites" type="radio" value="'.$item['id'].'" />';
10015
10016
            $icon_name = str_replace(' ', '', $item['item_type']);
10017
            if (file_exists('../img/lp_'.$icon_name.'.png')) {
10018
                $return .= Display::return_icon('lp_'.$icon_name.'.png');
10019
            } else {
10020
                if (file_exists('../img/lp_'.$icon_name.'.png')) {
10021
                    $return .= Display::return_icon('lp_'.$icon_name.'.png');
10022
                } else {
10023
                    $return .= Display::return_icon('folder_document.png');
10024
                }
10025
            }
10026
10027
            $return .= $item['title'].'</label>';
10028
            $return .= '</div>';
10029
            $return .= '</td>';
10030
10031
            if ($item['item_type'] == TOOL_QUIZ) {
10032
                // lets update max_score Quiz information depending of the Quiz Advanced properties
10033
                $lpItemObj = new LpItem($course_id, $item['id']);
10034
                $exercise = new Exercise($course_id);
10035
                $exercise->read($lpItemObj->path);
10036
                $lpItemObj->max_score = $exercise->get_max_score();
10037
                $lpItemObj->update();
10038
                $item['max_score'] = $lpItemObj->max_score;
10039
10040
                if (empty($selectedMinScoreValue) && !empty($masteryScoreAsMinValue)) {
10041
                    // Backwards compatibility with 1.9.x use mastery_score as min value
10042
                    $selectedMinScoreValue = $masteryScoreAsMinValue;
10043
                }
10044
10045
                $return .= '<td>';
10046
                $return .= '<input
10047
                    class="form-control"
10048
                    size="4" maxlength="3"
10049
                    name="min_'.$item['id'].'"
10050
                    type="number"
10051
                    min="0"
10052
                    step="any"
10053
                    max="'.$item['max_score'].'"
10054
                    value="'.$selectedMinScoreValue.'"
10055
                />';
10056
                $return .= '</td>';
10057
                $return .= '<td>';
10058
                $return .= '<input
10059
                    class="form-control"
10060
                    size="4"
10061
                    maxlength="3"
10062
                    name="max_'.$item['id'].'"
10063
                    type="number"
10064
                    min="0"
10065
                    step="any"
10066
                    max="'.$item['max_score'].'"
10067
                    value="'.$selectedMaxScoreValue.'"
10068
                />';
10069
                $return .= '</td>';
10070
            }
10071
10072
            if ($item['item_type'] == TOOL_HOTPOTATOES) {
10073
                $return .= '<td>';
10074
                $return .= '<input
10075
                    size="4"
10076
                    maxlength="3"
10077
                    name="min_'.$item['id'].'"
10078
                    type="number"
10079
                    min="0"
10080
                    step="any"
10081
                    max="'.$item['max_score'].'"
10082
                    value="'.$selectedMinScoreValue.'"
10083
                />';
10084
                $return .= '</td>';
10085
                $return .= '<td>';
10086
                $return .= '<input
10087
                    size="4"
10088
                    maxlength="3"
10089
                    name="max_'.$item['id'].'"
10090
                    type="number"
10091
                    min="0"
10092
                    step="any"
10093
                    max="'.$item['max_score'].'"
10094
                    value="'.$selectedMaxScoreValue.'"
10095
                />';
10096
                $return .= '</td>';
10097
            }
10098
            $return .= '</tr>';
10099
        }
10100
        $return .= '<tr>';
10101
        $return .= '</tr>';
10102
        $return .= '</tbody>';
10103
        $return .= '</table>';
10104
        $return .= '</div>';
10105
        $return .= '<div class="form-group">';
10106
        $return .= '<button class="btn btn-primary" name="submit_button" type="submit">'.
10107
            get_lang('ModifyPrerequisites').'</button>';
10108
        $return .= '</form>';
10109
10110
        return $return;
10111
    }
10112
10113
    /**
10114
     * Return HTML list to allow prerequisites selection for lp.
10115
     *
10116
     * @return string HTML form
10117
     */
10118
    public function display_lp_prerequisites_list()
10119
    {
10120
        $course_id = api_get_course_int_id();
10121
        $lp_id = $this->lp_id;
10122
        $tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
10123
10124
        // get current prerequisite
10125
        $sql = "SELECT * FROM $tbl_lp WHERE iid = $lp_id ";
10126
        $result = Database::query($sql);
10127
        $row = Database::fetch_array($result);
10128
        $prerequisiteId = $row['prerequisite'];
10129
        $session_id = api_get_session_id();
10130
        $session_condition = api_get_session_condition($session_id, true, true);
10131
        $sql = "SELECT * FROM $tbl_lp
10132
                WHERE c_id = $course_id $session_condition
10133
                ORDER BY display_order ";
10134
        $rs = Database::query($sql);
10135
        $return = '';
10136
        $return .= '<select name="prerequisites" class="form-control">';
10137
        $return .= '<option value="0">'.get_lang('None').'</option>';
10138
        if (Database::num_rows($rs) > 0) {
10139
            while ($row = Database::fetch_array($rs)) {
10140
                if ($row['id'] == $lp_id) {
10141
                    continue;
10142
                }
10143
                $return .= '<option value="'.$row['id'].'" '.(($row['id'] == $prerequisiteId) ? ' selected ' : '').'>'.$row['name'].'</option>';
10144
            }
10145
        }
10146
        $return .= '</select>';
10147
10148
        return $return;
10149
    }
10150
10151
    /**
10152
     * Creates a list with all the documents in it.
10153
     *
10154
     * @param bool $showInvisibleFiles
10155
     *
10156
     * @throws Exception
10157
     * @throws HTML_QuickForm_Error
10158
     *
10159
     * @return string
10160
     */
10161
    public function get_documents($showInvisibleFiles = false)
10162
    {
10163
        $course_info = api_get_course_info();
10164
        $sessionId = api_get_session_id();
10165
        $documentTree = DocumentManager::get_document_preview(
10166
            $course_info,
10167
            $this->lp_id,
10168
            null,
10169
            $sessionId,
10170
            true,
10171
            null,
10172
            null,
10173
            $showInvisibleFiles,
10174
            true
10175
        );
10176
10177
        $headers = [
10178
            get_lang('Files'),
10179
            get_lang('CreateTheDocument'),
10180
            get_lang('CreateReadOutText'),
10181
            get_lang('Upload'),
10182
        ];
10183
10184
        $form = new FormValidator(
10185
            'form_upload',
10186
            'POST',
10187
            $this->getCurrentBuildingModeURL(),
10188
            '',
10189
            ['enctype' => 'multipart/form-data']
10190
        );
10191
10192
        $folders = DocumentManager::get_all_document_folders(
10193
            api_get_course_info(),
10194
            0,
10195
            true
10196
        );
10197
10198
        $lpPathInfo = $this->generate_lp_folder(api_get_course_info());
10199
10200
        DocumentManager::build_directory_selector(
10201
            $folders,
10202
            $lpPathInfo['id'],
10203
            [],
10204
            true,
10205
            $form,
10206
            'directory_parent_id'
10207
        );
10208
10209
        $group = [
10210
            $form->createElement(
10211
                'radio',
10212
                'if_exists',
10213
                get_lang('UplWhatIfFileExists'),
10214
                get_lang('UplDoNothing'),
10215
                'nothing'
10216
            ),
10217
            $form->createElement(
10218
                'radio',
10219
                'if_exists',
10220
                null,
10221
                get_lang('UplOverwriteLong'),
10222
                'overwrite'
10223
            ),
10224
            $form->createElement(
10225
                'radio',
10226
                'if_exists',
10227
                null,
10228
                get_lang('UplRenameLong'),
10229
                'rename'
10230
            ),
10231
        ];
10232
        $form->addGroup($group, null, get_lang('UplWhatIfFileExists'));
10233
10234
        $fileExistsOption = api_get_setting('document_if_file_exists_option');
10235
        $defaultFileExistsOption = 'rename';
10236
        if (!empty($fileExistsOption)) {
10237
            $defaultFileExistsOption = $fileExistsOption;
10238
        }
10239
        $form->setDefaults(['if_exists' => $defaultFileExistsOption]);
10240
10241
        // Check box options
10242
        $form->addElement(
10243
            'checkbox',
10244
            'unzip',
10245
            get_lang('Options'),
10246
            get_lang('Uncompress')
10247
        );
10248
10249
        $url = api_get_path(WEB_AJAX_PATH).'document.ajax.php?'.api_get_cidreq().'&a=upload_file&curdirpath=';
10250
        $form->addMultipleUpload($url);
10251
        $new = $this->display_document_form('add', 0);
10252
        $frmReadOutText = $this->displayFrmReadOutText('add');
10253
        $tabs = Display::tabs(
10254
            $headers,
10255
            [$documentTree, $new, $frmReadOutText, $form->returnForm()],
10256
            'subtab'
10257
        );
10258
10259
        return $tabs;
10260
    }
10261
10262
    /**
10263
     * Creates a list with all the exercises (quiz) in it.
10264
     *
10265
     * @return string
10266
     */
10267
    public function get_exercises()
10268
    {
10269
        $course_id = api_get_course_int_id();
10270
        $session_id = api_get_session_id();
10271
        $userInfo = api_get_user_info();
10272
10273
        // New for hotpotatoes.
10274
        $uploadPath = DIR_HOTPOTATOES; //defined in main_api
10275
        $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
10276
        $tbl_quiz = Database::get_course_table(TABLE_QUIZ_TEST);
10277
        $condition_session = api_get_session_condition($session_id, true, true);
10278
        $setting = api_get_configuration_value('show_invisible_exercise_in_lp_list');
10279
10280
        $activeCondition = ' active <> -1 ';
10281
        if ($setting) {
10282
            $activeCondition = ' active = 1 ';
10283
        }
10284
10285
        $categoryCondition = '';
10286
        $categoryId = isset($_REQUEST['category_id']) ? (int) $_REQUEST['category_id'] : 0;
10287
        if (api_get_configuration_value('allow_exercise_categories') && !empty($categoryId)) {
10288
            $categoryCondition = " AND exercise_category_id = $categoryId ";
10289
        }
10290
10291
        $keywordCondition = '';
10292
        $keyword = isset($_REQUEST['keyword']) ? $_REQUEST['keyword'] : '';
10293
10294
        if (!empty($keyword)) {
10295
            $keyword = Database::escape_string($keyword);
10296
            $keywordCondition = " AND title LIKE '%$keyword%' ";
10297
        }
10298
10299
        $sql_quiz = "SELECT * FROM $tbl_quiz
10300
                     WHERE
10301
                            c_id = $course_id AND
10302
                            $activeCondition
10303
                            $condition_session
10304
                            $categoryCondition
10305
                            $keywordCondition
10306
                     ORDER BY title ASC";
10307
10308
        $sql_hot = "SELECT * FROM $tbl_doc
10309
                    WHERE
10310
                        c_id = $course_id AND
10311
                        path LIKE '".$uploadPath."/%/%htm%'
10312
                        $condition_session
10313
                     ORDER BY id ASC";
10314
10315
        $res_quiz = Database::query($sql_quiz);
10316
        $res_hot = Database::query($sql_hot);
10317
10318
        $currentUrl = api_get_self().'?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$this->lp_id.'#resource_tab-2';
10319
10320
        // Create a search-box
10321
        $form = new FormValidator('search_simple', 'get', $currentUrl);
10322
        $form->addHidden('action', 'add_item');
10323
        $form->addHidden('type', 'step');
10324
        $form->addHidden('lp_id', $this->lp_id);
10325
        $form->addHidden('lp_build_selected', '2');
10326
10327
        $form->addCourseHiddenParams();
10328
        $form->addText(
10329
            'keyword',
10330
            get_lang('Search'),
10331
            false,
10332
            [
10333
                'aria-label' => get_lang('Search'),
10334
            ]
10335
        );
10336
10337
        if (api_get_configuration_value('allow_exercise_categories')) {
10338
            $manager = new ExerciseCategoryManager();
10339
            $options = $manager->getCategoriesForSelect(api_get_course_int_id());
10340
            if (!empty($options)) {
10341
                $form->addSelect(
10342
                    'category_id',
10343
                    get_lang('Category'),
10344
                    $options,
10345
                    ['placeholder' => get_lang('SelectAnOption')]
10346
                );
10347
            }
10348
        }
10349
10350
        $form->addButtonSearch(get_lang('Search'));
10351
        $return = $form->returnForm();
10352
10353
        $return .= '<ul class="lp_resource">';
10354
10355
        $return .= '<li class="lp_resource_element">';
10356
        $return .= Display::return_icon('new_exercice.png');
10357
        $return .= '<a href="'.api_get_path(WEB_CODE_PATH).'exercise/exercise_admin.php?'.api_get_cidreq().'&lp_id='.$this->lp_id.'">'.
10358
            get_lang('NewExercise').'</a>';
10359
        $return .= '</li>';
10360
10361
        $previewIcon = Display::return_icon(
10362
            'preview_view.png',
10363
            get_lang('Preview')
10364
        );
10365
        $quizIcon = Display::return_icon('quiz.png', '', [], ICON_SIZE_TINY);
10366
        $moveIcon = Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10367
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/showinframes.php?'.api_get_cidreq();
10368
10369
        // Display hotpotatoes
10370
        while ($row_hot = Database::fetch_array($res_hot)) {
10371
            $link = Display::url(
10372
                $previewIcon,
10373
                $exerciseUrl.'&file='.$row_hot['path'],
10374
                ['target' => '_blank']
10375
            );
10376
            $return .= '<li class="lp_resource_element" data_id="'.$row_hot['id'].'" data_type="hotpotatoes" title="'.$row_hot['title'].'" >';
10377
            $return .= '<a class="moved" href="#">';
10378
            $return .= Display::return_icon(
10379
                'move_everywhere.png',
10380
                get_lang('Move'),
10381
                [],
10382
                ICON_SIZE_TINY
10383
            );
10384
            $return .= '</a> ';
10385
            $return .= Display::return_icon('hotpotatoes_s.png');
10386
            $return .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_HOTPOTATOES.'&file='.$row_hot['id'].'&lp_id='.$this->lp_id.'">'.
10387
                ((!empty($row_hot['comment'])) ? $row_hot['comment'] : Security::remove_XSS($row_hot['title'])).$link.'</a>';
10388
            $return .= '</li>';
10389
        }
10390
10391
        $exerciseUrl = api_get_path(WEB_CODE_PATH).'exercise/overview.php?'.api_get_cidreq();
10392
        while ($row_quiz = Database::fetch_array($res_quiz)) {
10393
            $title = strip_tags(
10394
                api_html_entity_decode($row_quiz['title'])
10395
            );
10396
10397
            $visibility = api_get_item_visibility(
10398
                ['real_id' => $course_id],
10399
                TOOL_QUIZ,
10400
                $row_quiz['iid'],
10401
                $session_id
10402
            );
10403
10404
            $link = Display::url(
10405
                $previewIcon,
10406
                $exerciseUrl.'&exerciseId='.$row_quiz['id'],
10407
                ['target' => '_blank']
10408
            );
10409
            $return .= '<li class="lp_resource_element" data_id="'.$row_quiz['id'].'" data_type="quiz" title="'.$title.'" >';
10410
            $return .= Display::url($moveIcon, '#', ['class' => 'moved']);
10411
            $return .= $quizIcon;
10412
            $sessionStar = api_get_session_image(
10413
                $row_quiz['session_id'],
10414
                $userInfo['status']
10415
            );
10416
            $return .= Display::url(
10417
                Security::remove_XSS(cut($title, 80)).$link.$sessionStar,
10418
                api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_QUIZ.'&file='.$row_quiz['id'].'&lp_id='.$this->lp_id,
10419
                [
10420
                    'class' => $visibility == 0 ? 'moved text-muted' : 'moved',
10421
                ]
10422
            );
10423
            $return .= '</li>';
10424
        }
10425
10426
        $return .= '</ul>';
10427
10428
        return $return;
10429
    }
10430
10431
    /**
10432
     * Creates a list with all the links in it.
10433
     *
10434
     * @return string
10435
     */
10436
    public function get_links()
10437
    {
10438
        $selfUrl = api_get_self();
10439
        $courseIdReq = api_get_cidreq();
10440
        $course = api_get_course_info();
10441
        $userInfo = api_get_user_info();
10442
10443
        $course_id = $course['real_id'];
10444
        $tbl_link = Database::get_course_table(TABLE_LINK);
10445
        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
10446
        $moveEverywhereIcon = Display::return_icon(
10447
            'move_everywhere.png',
10448
            get_lang('Move'),
10449
            [],
10450
            ICON_SIZE_TINY
10451
        );
10452
10453
        $session_id = api_get_session_id();
10454
        $condition_session = api_get_session_condition(
10455
            $session_id,
10456
            true,
10457
            true,
10458
            'link.session_id'
10459
        );
10460
10461
        $sql = "SELECT
10462
                    link.id as link_id,
10463
                    link.title as link_title,
10464
                    link.session_id as link_session_id,
10465
                    link.category_id as category_id,
10466
                    link_category.category_title as category_title
10467
                FROM $tbl_link as link
10468
                LEFT JOIN $linkCategoryTable as link_category
10469
                ON (link.category_id = link_category.id AND link.c_id = link_category.c_id)
10470
                WHERE link.c_id = $course_id $condition_session
10471
                ORDER BY link_category.category_title ASC, link.title ASC";
10472
        $result = Database::query($sql);
10473
        $categorizedLinks = [];
10474
        $categories = [];
10475
10476
        while ($link = Database::fetch_array($result)) {
10477
            if (!$link['category_id']) {
10478
                $link['category_title'] = get_lang('Uncategorized');
10479
            }
10480
            $categories[$link['category_id']] = $link['category_title'];
10481
            $categorizedLinks[$link['category_id']][$link['link_id']] = $link;
10482
        }
10483
10484
        $linksHtmlCode =
10485
            '<script>
10486
            function toggle_tool(tool, id) {
10487
                if(document.getElementById(tool+"_"+id+"_content").style.display == "none"){
10488
                    document.getElementById(tool+"_"+id+"_content").style.display = "block";
10489
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10490
                } else {
10491
                    document.getElementById(tool+"_"+id+"_content").style.display = "none";
10492
                    document.getElementById(tool+"_"+id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10493
                }
10494
            }
10495
        </script>
10496
10497
        <ul class="lp_resource">
10498
            <li class="lp_resource_element">
10499
                '.Display::return_icon('linksnew.gif').'
10500
                <a href="'.api_get_path(WEB_CODE_PATH).'link/link.php?'.$courseIdReq.'&action=addlink&lp_id='.$this->lp_id.'" title="'.get_lang('LinkAdd').'">'.
10501
                get_lang('LinkAdd').'
10502
                </a>
10503
            </li>';
10504
10505
        foreach ($categorizedLinks as $categoryId => $links) {
10506
            $linkNodes = null;
10507
            foreach ($links as $key => $linkInfo) {
10508
                $title = $linkInfo['link_title'];
10509
                $linkSessionId = $linkInfo['link_session_id'];
10510
10511
                $link = Display::url(
10512
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10513
                    api_get_path(WEB_CODE_PATH).'link/link_goto.php?'.api_get_cidreq().'&link_id='.$key,
10514
                    ['target' => '_blank']
10515
                );
10516
10517
                if (api_get_item_visibility($course, TOOL_LINK, $key, $session_id) != 2) {
10518
                    $sessionStar = api_get_session_image($linkSessionId, $userInfo['status']);
10519
                    $linkNodes .=
10520
                        '<li class="lp_resource_element" data_id="'.$key.'" data_type="'.TOOL_LINK.'" title="'.$title.'" >
10521
                        <a class="moved" href="#">'.
10522
                            $moveEverywhereIcon.
10523
                        '</a>
10524
                        '.Display::return_icon('links.png', '', [], ICON_SIZE_TINY).'
10525
                        <a class="moved" href="'.$selfUrl.'?'.$courseIdReq.'&action=add_item&type='.
10526
                        TOOL_LINK.'&file='.$key.'&lp_id='.$this->lp_id.'">'.
10527
                        Security::remove_XSS($title).$sessionStar.$link.
10528
                        '</a>
10529
                    </li>';
10530
                }
10531
            }
10532
            $linksHtmlCode .=
10533
                '<li>
10534
                <a style="cursor:hand" onclick="javascript: toggle_tool(\''.TOOL_LINK.'\','.$categoryId.')" style="vertical-align:middle">
10535
                    <img src="'.Display::returnIconPath('add.gif').'" id="'.TOOL_LINK.'_'.$categoryId.'_opener"
10536
                    align="absbottom" />
10537
                </a>
10538
                <span style="vertical-align:middle">'.Security::remove_XSS($categories[$categoryId]).'</span>
10539
            </li>
10540
            <div style="display:none" id="'.TOOL_LINK.'_'.$categoryId.'_content">'.$linkNodes.'</div>';
10541
        }
10542
        $linksHtmlCode .= '</ul>';
10543
10544
        return $linksHtmlCode;
10545
    }
10546
10547
    /**
10548
     * Creates a list with all the student publications in it.
10549
     *
10550
     * @return string
10551
     */
10552
    public function get_student_publications()
10553
    {
10554
        $return = '<ul class="lp_resource">';
10555
        $return .= '<li class="lp_resource_element">';
10556
        $return .= Display::return_icon('works_new.gif');
10557
        $return .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=add_item&type='.TOOL_STUDENTPUBLICATION.'&lp_id='.$this->lp_id.'">'.
10558
            get_lang('AddAssignmentPage').'</a>';
10559
        $return .= '</li>';
10560
10561
        require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
10562
        $works = getWorkListTeacher(0, 100, null, null, null);
10563
        if (!empty($works)) {
10564
            foreach ($works as $work) {
10565
                $link = Display::url(
10566
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10567
                    api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
10568
                    ['target' => '_blank']
10569
                );
10570
10571
                $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security::remove_XSS(cut(strip_tags($work['title']), 80)).'">';
10572
                $return .= '<a class="moved" href="#">';
10573
                $return .= Display::return_icon(
10574
                    'move_everywhere.png',
10575
                    get_lang('Move'),
10576
                    [],
10577
                    ICON_SIZE_TINY
10578
                );
10579
                $return .= '</a> ';
10580
10581
                $return .= Display::return_icon('works.png', '', [], ICON_SIZE_TINY);
10582
                $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.'">'.
10583
                    Security::remove_XSS(cut(strip_tags($work['title']), 80)).' '.$link.'
10584
                </a>';
10585
10586
                $return .= '</li>';
10587
            }
10588
        }
10589
10590
        $return .= '</ul>';
10591
10592
        return $return;
10593
    }
10594
10595
    /**
10596
     * Creates a list with all the forums in it.
10597
     *
10598
     * @return string
10599
     */
10600
    public function get_forums()
10601
    {
10602
        require_once '../forum/forumfunction.inc.php';
10603
10604
        $forumCategories = get_forum_categories();
10605
        $forumsInNoCategory = get_forums_in_category(0);
10606
        if (!empty($forumsInNoCategory)) {
10607
            $forumCategories = array_merge(
10608
                $forumCategories,
10609
                [
10610
                    [
10611
                        'cat_id' => 0,
10612
                        'session_id' => 0,
10613
                        'visibility' => 1,
10614
                        'cat_comment' => null,
10615
                    ],
10616
                ]
10617
            );
10618
        }
10619
10620
        $forumList = get_forums();
10621
        $a_forums = [];
10622
        foreach ($forumCategories as $forumCategory) {
10623
            // The forums in this category.
10624
            $forumsInCategory = get_forums_in_category($forumCategory['cat_id']);
10625
            if (!empty($forumsInCategory)) {
10626
                foreach ($forumList as $forum) {
10627
                    if (isset($forum['forum_category']) &&
10628
                        $forum['forum_category'] == $forumCategory['cat_id']
10629
                    ) {
10630
                        $a_forums[] = $forum;
10631
                    }
10632
                }
10633
            }
10634
        }
10635
10636
        $return = '<ul class="lp_resource">';
10637
10638
        // First add link
10639
        $return .= '<li class="lp_resource_element">';
10640
        $return .= Display::return_icon('new_forum.png');
10641
        $return .= Display::url(
10642
            get_lang('CreateANewForum'),
10643
            api_get_path(WEB_CODE_PATH).'forum/index.php?'.api_get_cidreq().'&'.http_build_query([
10644
                'action' => 'add',
10645
                'content' => 'forum',
10646
                'lp_id' => $this->lp_id,
10647
            ]),
10648
            ['title' => get_lang('CreateANewForum')]
10649
        );
10650
        $return .= '</li>';
10651
10652
        $return .= '<script>
10653
            function toggle_forum(forum_id) {
10654
                if (document.getElementById("forum_"+forum_id+"_content").style.display == "none") {
10655
                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
10656
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('remove.gif').'";
10657
                } else {
10658
                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
10659
                    document.getElementById("forum_"+forum_id+"_opener").src = "'.Display::returnIconPath('add.gif').'";
10660
                }
10661
            }
10662
        </script>';
10663
10664
        foreach ($a_forums as $forum) {
10665
            if (!empty($forum['forum_id'])) {
10666
                $link = Display::url(
10667
                    Display::return_icon('preview_view.png', get_lang('Preview')),
10668
                    api_get_path(WEB_CODE_PATH).'forum/viewforum.php?'.api_get_cidreq().'&forum='.$forum['forum_id'],
10669
                    ['target' => '_blank']
10670
                );
10671
10672
                $return .= '<li class="lp_resource_element" data_id="'.$forum['forum_id'].'" data_type="'.TOOL_FORUM.'" title="'.$forum['forum_title'].'" >';
10673
                $return .= '<a class="moved" href="#">';
10674
                $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10675
                $return .= ' </a>';
10676
                $return .= Display::return_icon('forum.png', '', [], ICON_SIZE_TINY);
10677
                $return .= '<a onclick="javascript:toggle_forum('.$forum['forum_id'].');" style="cursor:hand; vertical-align:middle">
10678
                                <img src="'.Display::returnIconPath('add.gif').'" id="forum_'.$forum['forum_id'].'_opener" align="absbottom" />
10679
                            </a>
10680
                            <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">'.
10681
                    Security::remove_XSS($forum['forum_title']).' '.$link.'</a>';
10682
10683
                $return .= '</li>';
10684
10685
                $return .= '<div style="display:none" id="forum_'.$forum['forum_id'].'_content">';
10686
                $a_threads = get_threads($forum['forum_id']);
10687
                if (is_array($a_threads)) {
10688
                    foreach ($a_threads as $thread) {
10689
                        $link = Display::url(
10690
                            Display::return_icon('preview_view.png', get_lang('Preview')),
10691
                            api_get_path(WEB_CODE_PATH).'forum/viewthread.php?'.api_get_cidreq().'&forum='.$forum['forum_id'].'&thread='.$thread['thread_id'],
10692
                            ['target' => '_blank']
10693
                        );
10694
10695
                        $return .= '<li class="lp_resource_element" data_id="'.$thread['thread_id'].'" data_type="'.TOOL_THREAD.'" title="'.$thread['thread_title'].'" >';
10696
                        $return .= '&nbsp;<a class="moved" href="#">';
10697
                        $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
10698
                        $return .= ' </a>';
10699
                        $return .= Display::return_icon('forumthread.png', get_lang('Thread'), [], ICON_SIZE_TINY);
10700
                        $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.'">'.
10701
                            Security::remove_XSS($thread['thread_title']).' '.$link.'</a>';
10702
                        $return .= '</li>';
10703
                    }
10704
                }
10705
                $return .= '</div>';
10706
            }
10707
        }
10708
        $return .= '</ul>';
10709
10710
        return $return;
10711
    }
10712
10713
    /**
10714
     * // TODO: The output encoding should be equal to the system encoding.
10715
     *
10716
     * Exports the learning path as a SCORM package. This is the main function that
10717
     * gathers the content, transforms it, writes the imsmanifest.xml file, zips the
10718
     * whole thing and returns the zip.
10719
     *
10720
     * This method needs to be called in PHP5, as it will fail with non-adequate
10721
     * XML package (like the ones for PHP4), and it is *not* a static method, so
10722
     * you need to call it on a learnpath object.
10723
     *
10724
     * @TODO The method might be redefined later on in the scorm class itself to avoid
10725
     * creating a SCORM structure if there is one already. However, if the initial SCORM
10726
     * path has been modified, it should use the generic method here below.
10727
     *
10728
     * @return string Returns the zip package string, or null if error
10729
     */
10730
    public function scormExport()
10731
    {
10732
        api_set_more_memory_and_time_limits();
10733
10734
        $_course = api_get_course_info();
10735
        $course_id = $_course['real_id'];
10736
        // Create the zip handler (this will remain available throughout the method).
10737
        $archivePath = api_get_path(SYS_ARCHIVE_PATH);
10738
        $sys_course_path = api_get_path(SYS_COURSE_PATH);
10739
        $temp_dir_short = uniqid('scorm_export', true);
10740
        $temp_zip_dir = $archivePath.'/'.$temp_dir_short;
10741
        $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip';
10742
        $zip_folder = new PclZip($temp_zip_file);
10743
        $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path();
10744
        $root_path = $main_path = api_get_path(SYS_PATH);
10745
        $files_cleanup = [];
10746
10747
        // Place to temporarily stash the zip file.
10748
        // create the temp dir if it doesn't exist
10749
        // or do a cleanup before creating the zip file.
10750
        if (!is_dir($temp_zip_dir)) {
10751
            mkdir($temp_zip_dir, api_get_permissions_for_new_directories());
10752
        } else {
10753
            // Cleanup: Check the temp dir for old files and delete them.
10754
            $handle = opendir($temp_zip_dir);
10755
            while (false !== ($file = readdir($handle))) {
10756
                if ($file != '.' && $file != '..') {
10757
                    unlink("$temp_zip_dir/$file");
10758
                }
10759
            }
10760
            closedir($handle);
10761
        }
10762
        $zip_files = $zip_files_abs = $zip_files_dist = [];
10763
        if (is_dir($current_course_path.'/scorm/'.$this->path) &&
10764
            is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')
10765
        ) {
10766
            // Remove the possible . at the end of the path.
10767
            $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path;
10768
            $dest_path_to_scorm_folder = str_replace('//', '/', $temp_zip_dir.'/scorm/'.$dest_path_to_lp);
10769
            mkdir(
10770
                $dest_path_to_scorm_folder,
10771
                api_get_permissions_for_new_directories(),
10772
                true
10773
            );
10774
            copyr(
10775
                $current_course_path.'/scorm/'.$this->path,
10776
                $dest_path_to_scorm_folder,
10777
                ['imsmanifest'],
10778
                $zip_files
10779
            );
10780
        }
10781
10782
        // Build a dummy imsmanifest structure.
10783
        // Do not add to the zip yet (we still need it).
10784
        // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content
10785
        // Aggregation Model official document, section "2.3 Content Packaging".
10786
        // We are going to build a UTF-8 encoded manifest.
10787
        // Later we will recode it to the desired (and supported) encoding.
10788
        $xmldoc = new DOMDocument('1.0');
10789
        $root = $xmldoc->createElement('manifest');
10790
        $root->setAttribute('identifier', 'SingleCourseManifest');
10791
        $root->setAttribute('version', '1.1');
10792
        $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2');
10793
        $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2');
10794
        $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
10795
        $root->setAttribute(
10796
            'xsi:schemaLocation',
10797
            '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'
10798
        );
10799
        // Build mandatory sub-root container elements.
10800
        $metadata = $xmldoc->createElement('metadata');
10801
        $md_schema = $xmldoc->createElement('schema', 'ADL SCORM');
10802
        $metadata->appendChild($md_schema);
10803
        $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2');
10804
        $metadata->appendChild($md_schemaversion);
10805
        $root->appendChild($metadata);
10806
10807
        $organizations = $xmldoc->createElement('organizations');
10808
        $resources = $xmldoc->createElement('resources');
10809
10810
        // Build the only organization we will use in building our learnpaths.
10811
        $organizations->setAttribute('default', 'chamilo_scorm_export');
10812
        $organization = $xmldoc->createElement('organization');
10813
        $organization->setAttribute('identifier', 'chamilo_scorm_export');
10814
        // To set the title of the SCORM entity (=organization), we take the name given
10815
        // in Chamilo and convert it to HTML entities using the Chamilo charset (not the
10816
        // learning path charset) as it is the encoding that defines how it is stored
10817
        // in the database. Then we convert it to HTML entities again as the "&" character
10818
        // alone is not authorized in XML (must be &amp;).
10819
        // The title is then decoded twice when extracting (see scorm::parse_manifest).
10820
        $org_title = $xmldoc->createElement('title', api_utf8_encode($this->get_name()));
10821
        $organization->appendChild($org_title);
10822
        $folder_name = 'document';
10823
10824
        // Removes the learning_path/scorm_folder path when exporting see #4841
10825
        $path_to_remove = '';
10826
        $path_to_replace = '';
10827
        $result = $this->generate_lp_folder($_course);
10828
        if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
10829
            $path_to_remove = 'document'.$result['dir'];
10830
            $path_to_replace = $folder_name.'/';
10831
        }
10832
10833
        // Fixes chamilo scorm exports
10834
        if ($this->ref === 'chamilo_scorm_export') {
10835
            $path_to_remove = 'scorm/'.$this->path.'/document/';
10836
        }
10837
10838
        // For each element, add it to the imsmanifest structure, then add it to the zip.
10839
        $link_updates = [];
10840
        $links_to_create = [];
10841
        foreach ($this->ordered_items as $index => $itemId) {
10842
            /** @var learnpathItem $item */
10843
            $item = $this->items[$itemId];
10844
            if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
10845
                // Get included documents from this item.
10846
                if ($item->type === 'sco') {
10847
                    $inc_docs = $item->get_resources_from_source(
10848
                        null,
10849
                        $current_course_path.'/scorm/'.$this->path.'/'.$item->get_path()
10850
                    );
10851
                } else {
10852
                    $inc_docs = $item->get_resources_from_source();
10853
                }
10854
10855
                // Give a child element <item> to the <organization> element.
10856
                $my_item_id = $item->get_id();
10857
                $my_item = $xmldoc->createElement('item');
10858
                $my_item->setAttribute('identifier', 'ITEM_'.$my_item_id);
10859
                $my_item->setAttribute('identifierref', 'RESOURCE_'.$my_item_id);
10860
                $my_item->setAttribute('isvisible', 'true');
10861
                // Give a child element <title> to the <item> element.
10862
                $my_title = $xmldoc->createElement(
10863
                    'title',
10864
                    htmlspecialchars(
10865
                        api_utf8_encode($item->get_title()),
10866
                        ENT_QUOTES,
10867
                        'UTF-8'
10868
                    )
10869
                );
10870
                $my_item->appendChild($my_title);
10871
                // Give a child element <adlcp:prerequisites> to the <item> element.
10872
                $my_prereqs = $xmldoc->createElement(
10873
                    'adlcp:prerequisites',
10874
                    $this->get_scorm_prereq_string($my_item_id)
10875
                );
10876
                $my_prereqs->setAttribute('type', 'aicc_script');
10877
                $my_item->appendChild($my_prereqs);
10878
                // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
10879
                //$xmldoc->createElement('adlcp:maxtimeallowed','');
10880
                // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
10881
                //$xmldoc->createElement('adlcp:timelimitaction','');
10882
                // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
10883
                //$xmldoc->createElement('adlcp:datafromlms','');
10884
                // Give a child element <adlcp:masteryscore> to the <item> element.
10885
                $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
10886
                $my_item->appendChild($my_masteryscore);
10887
10888
                // Attach this item to the organization element or hits parent if there is one.
10889
                if (!empty($item->parent) && $item->parent != 0) {
10890
                    $children = $organization->childNodes;
10891
                    $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
10892
                    if (is_object($possible_parent)) {
10893
                        $possible_parent->appendChild($my_item);
10894
                    } else {
10895
                        if ($this->debug > 0) {
10896
                            error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found');
10897
                        }
10898
                    }
10899
                } else {
10900
                    if ($this->debug > 0) {
10901
                        error_log('No parent');
10902
                    }
10903
                    $organization->appendChild($my_item);
10904
                }
10905
10906
                // Get the path of the file(s) from the course directory root.
10907
                $my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
10908
                $my_xml_file_path = $my_file_path;
10909
                if (!empty($path_to_remove)) {
10910
                    // From docs
10911
                    $my_xml_file_path = str_replace($path_to_remove, $path_to_replace, $my_file_path);
10912
10913
                    // From quiz
10914
                    if ($this->ref === 'chamilo_scorm_export') {
10915
                        $path_to_remove = 'scorm/'.$this->path.'/';
10916
                        $my_xml_file_path = str_replace($path_to_remove, '', $my_file_path);
10917
                    }
10918
                }
10919
10920
                $my_sub_dir = dirname($my_file_path);
10921
                $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
10922
                $my_xml_sub_dir = $my_sub_dir;
10923
                // Give a <resource> child to the <resources> element
10924
                $my_resource = $xmldoc->createElement('resource');
10925
                $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
10926
                $my_resource->setAttribute('type', 'webcontent');
10927
                $my_resource->setAttribute('href', $my_xml_file_path);
10928
                // adlcp:scormtype can be either 'sco' or 'asset'.
10929
                if ($item->type === 'sco') {
10930
                    $my_resource->setAttribute('adlcp:scormtype', 'sco');
10931
                } else {
10932
                    $my_resource->setAttribute('adlcp:scormtype', 'asset');
10933
                }
10934
                // xml:base is the base directory to find the files declared in this resource.
10935
                $my_resource->setAttribute('xml:base', '');
10936
                // Give a <file> child to the <resource> element.
10937
                $my_file = $xmldoc->createElement('file');
10938
                $my_file->setAttribute('href', $my_xml_file_path);
10939
                $my_resource->appendChild($my_file);
10940
10941
                // Dependency to other files - not yet supported.
10942
                $i = 1;
10943
                if ($inc_docs) {
10944
                    foreach ($inc_docs as $doc_info) {
10945
                        if (count($doc_info) < 1 || empty($doc_info[0])) {
10946
                            continue;
10947
                        }
10948
                        $my_dep = $xmldoc->createElement('resource');
10949
                        $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
10950
                        $my_dep->setAttribute('identifier', $res_id);
10951
                        $my_dep->setAttribute('type', 'webcontent');
10952
                        $my_dep->setAttribute('adlcp:scormtype', 'asset');
10953
                        $my_dep_file = $xmldoc->createElement('file');
10954
                        // Check type of URL.
10955
                        if ($doc_info[1] == 'remote') {
10956
                            // Remote file. Save url as is.
10957
                            $my_dep_file->setAttribute('href', $doc_info[0]);
10958
                            $my_dep->setAttribute('xml:base', '');
10959
                        } elseif ($doc_info[1] === 'local') {
10960
                            switch ($doc_info[2]) {
10961
                                case 'url':
10962
                                    // Local URL - save path as url for now, don't zip file.
10963
                                    $abs_path = api_get_path(SYS_PATH).
10964
                                        str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
10965
                                    $current_dir = dirname($abs_path);
10966
                                    $current_dir = str_replace('\\', '/', $current_dir);
10967
                                    $file_path = realpath($abs_path);
10968
                                    $file_path = str_replace('\\', '/', $file_path);
10969
                                    $my_dep_file->setAttribute('href', $file_path);
10970
                                    $my_dep->setAttribute('xml:base', '');
10971
                                    if (strstr($file_path, $main_path) !== false) {
10972
                                        // The calculated real path is really inside Chamilo's root path.
10973
                                        // Reduce file path to what's under the DocumentRoot.
10974
                                        $replace = $file_path;
10975
                                        $file_path = substr($file_path, strlen($root_path) - 1);
10976
                                        $destinationFile = $file_path;
10977
10978
                                        if (strstr($file_path, 'upload/users') !== false) {
10979
                                            $pos = strpos($file_path, 'my_files/');
10980
                                            if ($pos !== false) {
10981
                                                $onlyDirectory = str_replace(
10982
                                                    'upload/users/',
10983
                                                    '',
10984
                                                    substr($file_path, $pos, strlen($file_path))
10985
                                                );
10986
                                            }
10987
                                            $replace = $onlyDirectory;
10988
                                            $destinationFile = $replace;
10989
                                        }
10990
                                        $zip_files_abs[] = $file_path;
10991
                                        $link_updates[$my_file_path][] = [
10992
                                            'orig' => $doc_info[0],
10993
                                            'dest' => $destinationFile,
10994
                                            'replace' => $replace,
10995
                                        ];
10996
                                        $my_dep_file->setAttribute('href', $file_path);
10997
                                        $my_dep->setAttribute('xml:base', '');
10998
                                    } elseif (empty($file_path)) {
10999
                                        $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11000
                                        $file_path = str_replace('//', '/', $file_path);
11001
                                        if (file_exists($file_path)) {
11002
                                            // We get the relative path.
11003
                                            $file_path = substr($file_path, strlen($current_dir));
11004
                                            $zip_files[] = $my_sub_dir.'/'.$file_path;
11005
                                            $link_updates[$my_file_path][] = [
11006
                                                'orig' => $doc_info[0],
11007
                                                'dest' => $file_path,
11008
                                            ];
11009
                                            $my_dep_file->setAttribute('href', $file_path);
11010
                                            $my_dep->setAttribute('xml:base', '');
11011
                                        }
11012
                                    }
11013
                                    break;
11014
                                case 'abs':
11015
                                    // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11016
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11017
                                    $my_dep->setAttribute('xml:base', '');
11018
11019
                                    // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas
11020
                                    // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar
11021
                                    $abs_img_path_without_subdir = $doc_info[0];
11022
                                    $relp = api_get_path(REL_PATH); // The url-append config param.
11023
                                    $pos = strpos($abs_img_path_without_subdir, $relp);
11024
                                    if ($pos === 0) {
11025
                                        $abs_img_path_without_subdir = trim('/'.substr($abs_img_path_without_subdir, strlen($relp)));
11026
                                    }
11027
11028
                                    $file_path = realpath(api_get_path(SYS_APP_PATH).$abs_img_path_without_subdir);
11029
                                    $file_path = str_replace(['\\', '//'], '/', $file_path);
11030
11031
                                    // Prepare the current directory path (until just under 'document') with a trailing slash.
11032
                                    $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/';
11033
                                    // Check if the current document is in that path.
11034
                                    if (strstr($file_path, $cur_path) !== false) {
11035
                                        $destinationFile = substr($file_path, strlen($cur_path));
11036
                                        $filePathNoCoursePart = substr($file_path, strlen($cur_path));
11037
11038
                                        $fileToTest = $cur_path.$my_file_path;
11039
                                        if (!empty($path_to_remove)) {
11040
                                            $fileToTest = str_replace(
11041
                                                $path_to_remove.'/',
11042
                                                $path_to_replace,
11043
                                                $cur_path.$my_file_path
11044
                                            );
11045
                                        }
11046
11047
                                        $relative_path = api_get_relative_path($fileToTest, $file_path);
11048
11049
                                        // Put the current document in the zip (this array is the array
11050
                                        // that will manage documents already in the course folder - relative).
11051
                                        $zip_files[] = $filePathNoCoursePart;
11052
                                        // Update the links to the current document in the
11053
                                        // containing document (make them relative).
11054
                                        $link_updates[$my_file_path][] = [
11055
                                            'orig' => $doc_info[0],
11056
                                            'dest' => $destinationFile,
11057
                                            'replace' => $relative_path,
11058
                                        ];
11059
11060
                                        $my_dep_file->setAttribute('href', $file_path);
11061
                                        $my_dep->setAttribute('xml:base', '');
11062
                                    } elseif (strstr($file_path, $main_path) !== false) {
11063
                                        // The calculated real path is really inside Chamilo's root path.
11064
                                        // Reduce file path to what's under the DocumentRoot.
11065
                                        $file_path = substr($file_path, strlen($root_path));
11066
                                        $zip_files_abs[] = $file_path;
11067
                                        $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11068
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11069
                                        $my_dep->setAttribute('xml:base', '');
11070
                                    } elseif (empty($file_path)) {
11071
                                        // Probably this is an image inside "/main" directory
11072
                                        $file_path = api_get_path(SYS_PATH).$abs_img_path_without_subdir;
11073
                                        $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11074
11075
                                        if (file_exists($file_path)) {
11076
                                            if (strstr($file_path, 'main/default_course_document') !== false) {
11077
                                                // We get the relative path.
11078
                                                $pos = strpos($file_path, 'main/default_course_document/');
11079
                                                if ($pos !== false) {
11080
                                                    $onlyDirectory = str_replace(
11081
                                                        'main/default_course_document/',
11082
                                                        '',
11083
                                                        substr($file_path, $pos, strlen($file_path))
11084
                                                    );
11085
                                                }
11086
11087
                                                $destinationFile = 'default_course_document/'.$onlyDirectory;
11088
                                                $fileAbs = substr($file_path, strlen(api_get_path(SYS_PATH)));
11089
                                                $zip_files_abs[] = $fileAbs;
11090
                                                $link_updates[$my_file_path][] = [
11091
                                                    'orig' => $doc_info[0],
11092
                                                    'dest' => $destinationFile,
11093
                                                ];
11094
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11095
                                                $my_dep->setAttribute('xml:base', '');
11096
                                            }
11097
                                        }
11098
                                    }
11099
                                    break;
11100
                                case 'rel':
11101
                                    // Path relative to the current document.
11102
                                    // Save xml:base as current document's directory and save file in zip as subdir.file_path
11103
                                    if (substr($doc_info[0], 0, 2) === '..') {
11104
                                        // Relative path going up.
11105
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11106
                                        $current_dir = str_replace('\\', '/', $current_dir);
11107
                                        $file_path = realpath($current_dir.$doc_info[0]);
11108
                                        $file_path = str_replace('\\', '/', $file_path);
11109
                                        if (strstr($file_path, $main_path) !== false) {
11110
                                            // The calculated real path is really inside Chamilo's root path.
11111
                                            // Reduce file path to what's under the DocumentRoot.
11112
                                            $file_path = substr($file_path, strlen($root_path));
11113
                                            $zip_files_abs[] = $file_path;
11114
                                            $link_updates[$my_file_path][] = ['orig' => $doc_info[0], 'dest' => $file_path];
11115
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11116
                                            $my_dep->setAttribute('xml:base', '');
11117
                                        }
11118
                                    } else {
11119
                                        $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11120
                                        $my_dep_file->setAttribute('href', $doc_info[0]);
11121
                                        $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11122
                                    }
11123
                                    break;
11124
                                default:
11125
                                    $my_dep_file->setAttribute('href', $doc_info[0]);
11126
                                    $my_dep->setAttribute('xml:base', '');
11127
                                    break;
11128
                            }
11129
                        }
11130
                        $my_dep->appendChild($my_dep_file);
11131
                        $resources->appendChild($my_dep);
11132
                        $dependency = $xmldoc->createElement('dependency');
11133
                        $dependency->setAttribute('identifierref', $res_id);
11134
                        $my_resource->appendChild($dependency);
11135
                        $i++;
11136
                    }
11137
                }
11138
                $resources->appendChild($my_resource);
11139
                $zip_files[] = $my_file_path;
11140
            } else {
11141
                // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it.
11142
                switch ($item->type) {
11143
                    case TOOL_LINK:
11144
                        $my_item = $xmldoc->createElement('item');
11145
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11146
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11147
                        $my_item->setAttribute('isvisible', 'true');
11148
                        // Give a child element <title> to the <item> element.
11149
                        $my_title = $xmldoc->createElement(
11150
                            'title',
11151
                            htmlspecialchars(
11152
                                api_utf8_encode($item->get_title()),
11153
                                ENT_QUOTES,
11154
                                'UTF-8'
11155
                            )
11156
                        );
11157
                        $my_item->appendChild($my_title);
11158
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11159
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11160
                        $my_prereqs->setAttribute('type', 'aicc_script');
11161
                        $my_item->appendChild($my_prereqs);
11162
                        // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported.
11163
                        //$xmldoc->createElement('adlcp:maxtimeallowed', '');
11164
                        // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported.
11165
                        //$xmldoc->createElement('adlcp:timelimitaction', '');
11166
                        // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported.
11167
                        //$xmldoc->createElement('adlcp:datafromlms', '');
11168
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11169
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11170
                        $my_item->appendChild($my_masteryscore);
11171
11172
                        // Attach this item to the organization element or its parent if there is one.
11173
                        if (!empty($item->parent) && $item->parent != 0) {
11174
                            $children = $organization->childNodes;
11175
                            for ($i = 0; $i < $children->length; $i++) {
11176
                                $item_temp = $children->item($i);
11177
                                if ($item_temp->nodeName == 'item') {
11178
                                    if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) {
11179
                                        $item_temp->appendChild($my_item);
11180
                                    }
11181
                                }
11182
                            }
11183
                        } else {
11184
                            $organization->appendChild($my_item);
11185
                        }
11186
11187
                        $my_file_path = 'link_'.$item->get_id().'.html';
11188
                        $sql = 'SELECT url, title FROM '.Database::get_course_table(TABLE_LINK).'
11189
                                WHERE c_id = '.$course_id.' AND id = '.$item->path;
11190
                        $rs = Database::query($sql);
11191
                        if ($link = Database::fetch_array($rs)) {
11192
                            $url = $link['url'];
11193
                            $title = stripslashes($link['title']);
11194
                            $links_to_create[$my_file_path] = ['title' => $title, 'url' => $url];
11195
                            $my_xml_file_path = $my_file_path;
11196
                            $my_sub_dir = dirname($my_file_path);
11197
                            $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11198
                            $my_xml_sub_dir = $my_sub_dir;
11199
                            // Give a <resource> child to the <resources> element.
11200
                            $my_resource = $xmldoc->createElement('resource');
11201
                            $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11202
                            $my_resource->setAttribute('type', 'webcontent');
11203
                            $my_resource->setAttribute('href', $my_xml_file_path);
11204
                            // adlcp:scormtype can be either 'sco' or 'asset'.
11205
                            $my_resource->setAttribute('adlcp:scormtype', 'asset');
11206
                            // xml:base is the base directory to find the files declared in this resource.
11207
                            $my_resource->setAttribute('xml:base', '');
11208
                            // give a <file> child to the <resource> element.
11209
                            $my_file = $xmldoc->createElement('file');
11210
                            $my_file->setAttribute('href', $my_xml_file_path);
11211
                            $my_resource->appendChild($my_file);
11212
                            $resources->appendChild($my_resource);
11213
                        }
11214
                        break;
11215
                    case TOOL_QUIZ:
11216
                        $exe_id = $item->path;
11217
                        // Should be using ref when everything will be cleaned up in this regard.
11218
                        $exe = new Exercise();
11219
                        $exe->read($exe_id);
11220
                        $my_item = $xmldoc->createElement('item');
11221
                        $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id());
11222
                        $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id());
11223
                        $my_item->setAttribute('isvisible', 'true');
11224
                        // Give a child element <title> to the <item> element.
11225
                        $my_title = $xmldoc->createElement(
11226
                            'title',
11227
                            htmlspecialchars(
11228
                                api_utf8_encode($item->get_title()),
11229
                                ENT_QUOTES,
11230
                                'UTF-8'
11231
                            )
11232
                        );
11233
                        $my_item->appendChild($my_title);
11234
                        $my_max_score = $xmldoc->createElement('max_score', $item->get_max());
11235
                        $my_item->appendChild($my_max_score);
11236
                        // Give a child element <adlcp:prerequisites> to the <item> element.
11237
                        $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string());
11238
                        $my_prereqs->setAttribute('type', 'aicc_script');
11239
                        $my_item->appendChild($my_prereqs);
11240
                        // Give a child element <adlcp:masteryscore> to the <item> element.
11241
                        $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score());
11242
                        $my_item->appendChild($my_masteryscore);
11243
11244
                        // Attach this item to the organization element or hits parent if there is one.
11245
                        if (!empty($item->parent) && $item->parent != 0) {
11246
                            $children = $organization->childNodes;
11247
                            $possible_parent = $this->get_scorm_xml_node($children, 'ITEM_'.$item->parent);
11248
                            if ($possible_parent) {
11249
                                if ($possible_parent->getAttribute('identifier') === 'ITEM_'.$item->parent) {
11250
                                    $possible_parent->appendChild($my_item);
11251
                                }
11252
                            }
11253
                        } else {
11254
                            $organization->appendChild($my_item);
11255
                        }
11256
11257
                        // Get the path of the file(s) from the course directory root
11258
                        //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/');
11259
                        $my_file_path = 'quiz_'.$item->get_id().'.html';
11260
                        // Write the contents of the exported exercise into a (big) html file
11261
                        // to later pack it into the exported SCORM. The file will be removed afterwards.
11262
                        $scormExercise = new ScormExercise($exe, true);
11263
                        $contents = $scormExercise->export();
11264
11265
                        $tmp_file_path = $archivePath.$temp_dir_short.'/'.$my_file_path;
11266
                        $res = file_put_contents($tmp_file_path, $contents);
11267
                        if ($res === false) {
11268
                            error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0);
11269
                        }
11270
                        $files_cleanup[] = $tmp_file_path;
11271
                        $my_xml_file_path = $my_file_path;
11272
                        $my_sub_dir = dirname($my_file_path);
11273
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11274
                        $my_xml_sub_dir = $my_sub_dir;
11275
                        // Give a <resource> child to the <resources> element.
11276
                        $my_resource = $xmldoc->createElement('resource');
11277
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11278
                        $my_resource->setAttribute('type', 'webcontent');
11279
                        $my_resource->setAttribute('href', $my_xml_file_path);
11280
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11281
                        $my_resource->setAttribute('adlcp:scormtype', 'sco');
11282
                        // xml:base is the base directory to find the files declared in this resource.
11283
                        $my_resource->setAttribute('xml:base', '');
11284
                        // Give a <file> child to the <resource> element.
11285
                        $my_file = $xmldoc->createElement('file');
11286
                        $my_file->setAttribute('href', $my_xml_file_path);
11287
                        $my_resource->appendChild($my_file);
11288
11289
                        // Get included docs.
11290
                        $inc_docs = $item->get_resources_from_source(null, $tmp_file_path);
11291
11292
                        // Dependency to other files - not yet supported.
11293
                        $i = 1;
11294
                        foreach ($inc_docs as $doc_info) {
11295
                            if (count($doc_info) < 1 || empty($doc_info[0])) {
11296
                                continue;
11297
                            }
11298
                            $my_dep = $xmldoc->createElement('resource');
11299
                            $res_id = 'RESOURCE_'.$item->get_id().'_'.$i;
11300
                            $my_dep->setAttribute('identifier', $res_id);
11301
                            $my_dep->setAttribute('type', 'webcontent');
11302
                            $my_dep->setAttribute('adlcp:scormtype', 'asset');
11303
                            $my_dep_file = $xmldoc->createElement('file');
11304
                            // Check type of URL.
11305
                            if ($doc_info[1] == 'remote') {
11306
                                // Remote file. Save url as is.
11307
                                $my_dep_file->setAttribute('href', $doc_info[0]);
11308
                                $my_dep->setAttribute('xml:base', '');
11309
                            } elseif ($doc_info[1] == 'local') {
11310
                                switch ($doc_info[2]) {
11311
                                    case 'url': // Local URL - save path as url for now, don't zip file.
11312
                                        // Save file but as local file (retrieve from URL).
11313
                                        $abs_path = api_get_path(SYS_PATH).
11314
                                            str_replace(api_get_path(WEB_PATH), '', $doc_info[0]);
11315
                                        $current_dir = dirname($abs_path);
11316
                                        $current_dir = str_replace('\\', '/', $current_dir);
11317
                                        $file_path = realpath($abs_path);
11318
                                        $file_path = str_replace('\\', '/', $file_path);
11319
                                        $my_dep_file->setAttribute('href', 'document/'.$file_path);
11320
                                        $my_dep->setAttribute('xml:base', '');
11321
                                        if (strstr($file_path, $main_path) !== false) {
11322
                                            // The calculated real path is really inside the chamilo root path.
11323
                                            // Reduce file path to what's under the DocumentRoot.
11324
                                            $file_path = substr($file_path, strlen($root_path));
11325
                                            $zip_files_abs[] = $file_path;
11326
                                            $link_updates[$my_file_path][] = [
11327
                                                'orig' => $doc_info[0],
11328
                                                'dest' => 'document/'.$file_path,
11329
                                            ];
11330
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11331
                                            $my_dep->setAttribute('xml:base', '');
11332
                                        } elseif (empty($file_path)) {
11333
                                            $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path;
11334
                                            $file_path = str_replace('//', '/', $file_path);
11335
                                            if (file_exists($file_path)) {
11336
                                                $file_path = substr($file_path, strlen($current_dir));
11337
                                                // We get the relative path.
11338
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11339
                                                $link_updates[$my_file_path][] = [
11340
                                                    'orig' => $doc_info[0],
11341
                                                    'dest' => 'document/'.$file_path,
11342
                                                ];
11343
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11344
                                                $my_dep->setAttribute('xml:base', '');
11345
                                            }
11346
                                        }
11347
                                        break;
11348
                                    case 'abs':
11349
                                        // Absolute path from DocumentRoot. Save file and leave path as is in the zip.
11350
                                        $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11351
                                        $current_dir = str_replace('\\', '/', $current_dir);
11352
                                        $file_path = realpath($doc_info[0]);
11353
                                        $file_path = str_replace('\\', '/', $file_path);
11354
                                        $my_dep_file->setAttribute('href', $file_path);
11355
                                        $my_dep->setAttribute('xml:base', '');
11356
11357
                                        if (strstr($file_path, $main_path) !== false) {
11358
                                            // The calculated real path is really inside the chamilo root path.
11359
                                            // Reduce file path to what's under the DocumentRoot.
11360
                                            $file_path = substr($file_path, strlen($root_path));
11361
                                            $zip_files_abs[] = $file_path;
11362
                                            $link_updates[$my_file_path][] = [
11363
                                                'orig' => $doc_info[0],
11364
                                                'dest' => $file_path,
11365
                                            ];
11366
                                            $my_dep_file->setAttribute('href', 'document/'.$file_path);
11367
                                            $my_dep->setAttribute('xml:base', '');
11368
                                        } elseif (empty($file_path)) {
11369
                                            $docSysPartPath = str_replace(
11370
                                                api_get_path(REL_COURSE_PATH),
11371
                                                '',
11372
                                                $doc_info[0]
11373
                                            );
11374
11375
                                            $docSysPartPathNoCourseCode = str_replace(
11376
                                                $_course['directory'].'/',
11377
                                                '',
11378
                                                $docSysPartPath
11379
                                            );
11380
11381
                                            $docSysPath = api_get_path(SYS_COURSE_PATH).$docSysPartPath;
11382
                                            if (file_exists($docSysPath)) {
11383
                                                $file_path = $docSysPartPathNoCourseCode;
11384
                                                $zip_files[] = $my_sub_dir.'/'.$file_path;
11385
                                                $link_updates[$my_file_path][] = [
11386
                                                    'orig' => $doc_info[0],
11387
                                                    'dest' => $file_path,
11388
                                                ];
11389
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11390
                                                $my_dep->setAttribute('xml:base', '');
11391
                                            }
11392
                                        }
11393
                                        break;
11394
                                    case 'rel':
11395
                                        // Path relative to the current document. Save xml:base as current document's
11396
                                        // directory and save file in zip as subdir.file_path
11397
                                        if (substr($doc_info[0], 0, 2) === '..') {
11398
                                            // Relative path going up.
11399
                                            $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/';
11400
                                            $current_dir = str_replace('\\', '/', $current_dir);
11401
                                            $file_path = realpath($current_dir.$doc_info[0]);
11402
                                            $file_path = str_replace('\\', '/', $file_path);
11403
                                            if (strstr($file_path, $main_path) !== false) {
11404
                                                // The calculated real path is really inside Chamilo's root path.
11405
                                                // Reduce file path to what's under the DocumentRoot.
11406
11407
                                                $file_path = substr($file_path, strlen($root_path));
11408
                                                $file_path_dest = $file_path;
11409
11410
                                                // File path is courses/CHAMILO/document/....
11411
                                                $info_file_path = explode('/', $file_path);
11412
                                                if ($info_file_path[0] == 'courses') {
11413
                                                    // Add character "/" in file path.
11414
                                                    $file_path_dest = 'document/'.$file_path;
11415
                                                }
11416
                                                $zip_files_abs[] = $file_path;
11417
11418
                                                $link_updates[$my_file_path][] = [
11419
                                                    'orig' => $doc_info[0],
11420
                                                    'dest' => $file_path_dest,
11421
                                                ];
11422
                                                $my_dep_file->setAttribute('href', 'document/'.$file_path);
11423
                                                $my_dep->setAttribute('xml:base', '');
11424
                                            }
11425
                                        } else {
11426
                                            $zip_files[] = $my_sub_dir.'/'.$doc_info[0];
11427
                                            $my_dep_file->setAttribute('href', $doc_info[0]);
11428
                                            $my_dep->setAttribute('xml:base', $my_xml_sub_dir);
11429
                                        }
11430
                                        break;
11431
                                    default:
11432
                                        $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/
11433
                                        $my_dep->setAttribute('xml:base', '');
11434
                                        break;
11435
                                }
11436
                            }
11437
                            $my_dep->appendChild($my_dep_file);
11438
                            $resources->appendChild($my_dep);
11439
                            $dependency = $xmldoc->createElement('dependency');
11440
                            $dependency->setAttribute('identifierref', $res_id);
11441
                            $my_resource->appendChild($dependency);
11442
                            $i++;
11443
                        }
11444
                        $resources->appendChild($my_resource);
11445
                        $zip_files[] = $my_file_path;
11446
                        break;
11447
                    default:
11448
                        // Get the path of the file(s) from the course directory root
11449
                        $my_file_path = 'non_exportable.html';
11450
                        //$my_xml_file_path = api_htmlentities(api_utf8_encode($my_file_path), ENT_COMPAT, 'UTF-8');
11451
                        $my_xml_file_path = $my_file_path;
11452
                        $my_sub_dir = dirname($my_file_path);
11453
                        $my_sub_dir = str_replace('\\', '/', $my_sub_dir);
11454
                        //$my_xml_sub_dir = api_htmlentities(api_utf8_encode($my_sub_dir), ENT_COMPAT, 'UTF-8');
11455
                        $my_xml_sub_dir = $my_sub_dir;
11456
                        // Give a <resource> child to the <resources> element.
11457
                        $my_resource = $xmldoc->createElement('resource');
11458
                        $my_resource->setAttribute('identifier', 'RESOURCE_'.$item->get_id());
11459
                        $my_resource->setAttribute('type', 'webcontent');
11460
                        $my_resource->setAttribute('href', $folder_name.'/'.$my_xml_file_path);
11461
                        // adlcp:scormtype can be either 'sco' or 'asset'.
11462
                        $my_resource->setAttribute('adlcp:scormtype', 'asset');
11463
                        // xml:base is the base directory to find the files declared in this resource.
11464
                        $my_resource->setAttribute('xml:base', '');
11465
                        // Give a <file> child to the <resource> element.
11466
                        $my_file = $xmldoc->createElement('file');
11467
                        $my_file->setAttribute('href', 'document/'.$my_xml_file_path);
11468
                        $my_resource->appendChild($my_file);
11469
                        $resources->appendChild($my_resource);
11470
                        break;
11471
                }
11472
            }
11473
        }
11474
        $organizations->appendChild($organization);
11475
        $root->appendChild($organizations);
11476
        $root->appendChild($resources);
11477
        $xmldoc->appendChild($root);
11478
11479
        $copyAll = api_get_configuration_value('add_all_files_in_lp_export');
11480
11481
        // then add the file to the zip, then destroy the file (this is done automatically).
11482
        // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138
11483
        foreach ($zip_files as $file_path) {
11484
            if (empty($file_path)) {
11485
                continue;
11486
            }
11487
11488
            $filePath = $sys_course_path.$_course['path'].'/'.$file_path;
11489
            $dest_file = $archivePath.$temp_dir_short.'/'.$file_path;
11490
11491
            if (!empty($path_to_remove) && !empty($path_to_replace)) {
11492
                $dest_file = str_replace($path_to_remove, $path_to_replace, $dest_file);
11493
            }
11494
11495
            $this->create_path($dest_file);
11496
            @copy($filePath, $dest_file);
11497
11498
            // Check if the file needs a link update.
11499
            if (in_array($file_path, array_keys($link_updates))) {
11500
                $string = file_get_contents($dest_file);
11501
                unlink($dest_file);
11502
                foreach ($link_updates[$file_path] as $old_new) {
11503
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11504
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11505
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11506
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11507
                    if (substr($old_new['dest'], -3) === 'flv' &&
11508
                        substr($old_new['dest'], 0, 5) === 'main/'
11509
                    ) {
11510
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11511
                    } elseif (substr($old_new['dest'], -3) === 'flv' &&
11512
                        substr($old_new['dest'], 0, 6) === 'video/'
11513
                    ) {
11514
                        $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']);
11515
                    }
11516
11517
                    // Fix to avoid problems with default_course_document
11518
                    if (strpos('main/default_course_document', $old_new['dest']) === false) {
11519
                        $newDestination = $old_new['dest'];
11520
                        if (isset($old_new['replace']) && !empty($old_new['replace'])) {
11521
                            $newDestination = $old_new['replace'];
11522
                        }
11523
                    } else {
11524
                        $newDestination = str_replace('document/', '', $old_new['dest']);
11525
                    }
11526
                    $string = str_replace($old_new['orig'], $newDestination, $string);
11527
11528
                    // Add files inside the HTMLs
11529
                    $new_path = str_replace(api_get_path(REL_COURSE_PATH), '', $old_new['orig']);
11530
                    $destinationFile = $archivePath.$temp_dir_short.'/'.$old_new['dest'];
11531
                    if (file_exists($sys_course_path.$new_path) && is_file($sys_course_path.$new_path)) {
11532
                        copy(
11533
                            $sys_course_path.$new_path,
11534
                            $destinationFile
11535
                        );
11536
                    }
11537
                }
11538
                file_put_contents($dest_file, $string);
11539
            }
11540
11541
            if (file_exists($filePath) && $copyAll) {
11542
                $extension = $this->get_extension($filePath);
11543
                if (in_array($extension, ['html', 'html'])) {
11544
                    $containerOrigin = dirname($filePath);
11545
                    $containerDestination = dirname($dest_file);
11546
11547
                    $finder = new Finder();
11548
                    $finder->files()->in($containerOrigin)
11549
                        ->notName('*_DELETED_*')
11550
                        ->exclude('share_folder')
11551
                        ->exclude('chat_files')
11552
                        ->exclude('certificates')
11553
                    ;
11554
11555
                    if (is_dir($containerOrigin) &&
11556
                        is_dir($containerDestination)
11557
                    ) {
11558
                        $fs = new Filesystem();
11559
                        $fs->mirror(
11560
                            $containerOrigin,
11561
                            $containerDestination,
11562
                            $finder
11563
                        );
11564
                    }
11565
                }
11566
            }
11567
        }
11568
11569
        foreach ($zip_files_abs as $file_path) {
11570
            if (empty($file_path)) {
11571
                continue;
11572
            }
11573
11574
            if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) {
11575
                continue;
11576
            }
11577
11578
            $dest_file = $archivePath.$temp_dir_short.'/document/'.$file_path;
11579
            if (strstr($file_path, 'upload/users') !== false) {
11580
                $pos = strpos($file_path, 'my_files/');
11581
                if ($pos !== false) {
11582
                    $onlyDirectory = str_replace(
11583
                        'upload/users/',
11584
                        '',
11585
                        substr($file_path, $pos, strlen($file_path))
11586
                    );
11587
                    $dest_file = $archivePath.$temp_dir_short.'/document/'.$onlyDirectory;
11588
                }
11589
            }
11590
11591
            if (strstr($file_path, 'default_course_document/') !== false) {
11592
                $replace = str_replace('/main', '', $file_path);
11593
                $dest_file = $archivePath.$temp_dir_short.'/document/'.$replace;
11594
            }
11595
11596
            if (empty($dest_file)) {
11597
                continue;
11598
            }
11599
11600
            $this->create_path($dest_file);
11601
            copy($main_path.$file_path, $dest_file);
11602
            // Check if the file needs a link update.
11603
            if (in_array($file_path, array_keys($link_updates))) {
11604
                $string = file_get_contents($dest_file);
11605
                unlink($dest_file);
11606
                foreach ($link_updates[$file_path] as $old_new) {
11607
                    // This is an ugly hack that allows .flv files to be found by the flv player that
11608
                    // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs
11609
                    // to find the flv to play in document/main/, so we replace main/ in the flv path by
11610
                    // ../../.. to return from inc/lib/flv_player to the document/main path.
11611
                    if (substr($old_new['dest'], -3) == 'flv' &&
11612
                        substr($old_new['dest'], 0, 5) == 'main/'
11613
                    ) {
11614
                        $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']);
11615
                    }
11616
                    $string = str_replace($old_new['orig'], $old_new['dest'], $string);
11617
                }
11618
                file_put_contents($dest_file, $string);
11619
            }
11620
        }
11621
11622
        if (is_array($links_to_create)) {
11623
            foreach ($links_to_create as $file => $link) {
11624
                $content = '<!DOCTYPE html><head>
11625
                            <meta charset="'.api_get_language_isocode().'" />
11626
                            <title>'.$link['title'].'</title>
11627
                            </head>
11628
                            <body dir="'.api_get_text_direction().'">
11629
                            <div style="text-align:center">
11630
                            <a href="'.$link['url'].'">'.$link['title'].'</a></div>
11631
                            </body>
11632
                            </html>';
11633
                file_put_contents($archivePath.$temp_dir_short.'/'.$file, $content);
11634
            }
11635
        }
11636
11637
        // Add non exportable message explanation.
11638
        $lang_not_exportable = get_lang('ThisItemIsNotExportable');
11639
        $file_content = '<!DOCTYPE html><head>
11640
                        <meta charset="'.api_get_language_isocode().'" />
11641
                        <title>'.$lang_not_exportable.'</title>
11642
                        <meta http-equiv="Content-Type" content="text/html; charset='.api_get_system_encoding().'" />
11643
                        </head>
11644
                        <body dir="'.api_get_text_direction().'">';
11645
        $file_content .=
11646
            <<<EOD
11647
                    <style>
11648
            .error-message {
11649
                font-family: arial, verdana, helvetica, sans-serif;
11650
                border-width: 1px;
11651
                border-style: solid;
11652
                left: 50%;
11653
                margin: 10px auto;
11654
                min-height: 30px;
11655
                padding: 5px;
11656
                right: 50%;
11657
                width: 500px;
11658
                background-color: #FFD1D1;
11659
                border-color: #FF0000;
11660
                color: #000;
11661
            }
11662
        </style>
11663
    <body>
11664
        <div class="error-message">
11665
            $lang_not_exportable
11666
        </div>
11667
    </body>
11668
</html>
11669
EOD;
11670
        if (!is_dir($archivePath.$temp_dir_short.'/document')) {
11671
            @mkdir($archivePath.$temp_dir_short.'/document', api_get_permissions_for_new_directories());
11672
        }
11673
        file_put_contents($archivePath.$temp_dir_short.'/document/non_exportable.html', $file_content);
11674
11675
        // Add the extra files that go along with a SCORM package.
11676
        $main_code_path = api_get_path(SYS_CODE_PATH).'lp/packaging/';
11677
11678
        $fs = new Filesystem();
11679
        $fs->mirror($main_code_path, $archivePath.$temp_dir_short);
11680
11681
        // Finalize the imsmanifest structure, add to the zip, then return the zip.
11682
        $manifest = @$xmldoc->saveXML();
11683
        $manifest = api_utf8_decode_xml($manifest); // The manifest gets the system encoding now.
11684
        file_put_contents($archivePath.'/'.$temp_dir_short.'/imsmanifest.xml', $manifest);
11685
        $zip_folder->add(
11686
            $archivePath.'/'.$temp_dir_short,
11687
            PCLZIP_OPT_REMOVE_PATH,
11688
            $archivePath.'/'.$temp_dir_short.'/'
11689
        );
11690
11691
        // Clean possible temporary files.
11692
        foreach ($files_cleanup as $file) {
11693
            $res = unlink($file);
11694
            if ($res === false) {
11695
                error_log(
11696
                    'Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__,
11697
                    0
11698
                );
11699
            }
11700
        }
11701
        $name = api_replace_dangerous_char($this->get_name()).'.zip';
11702
        DocumentManager::file_send_for_download($temp_zip_file, true, $name);
11703
    }
11704
11705
    /**
11706
     * @param int $lp_id
11707
     *
11708
     * @return bool
11709
     */
11710
    public function scorm_export_to_pdf($lp_id)
11711
    {
11712
        $lp_id = (int) $lp_id;
11713
        $files_to_export = [];
11714
11715
        $sessionId = api_get_session_id();
11716
        $course_data = api_get_course_info($this->cc);
11717
11718
        if (!empty($course_data)) {
11719
            $scorm_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/scorm/'.$this->path;
11720
            $list = self::get_flat_ordered_items_list($lp_id);
11721
            if (!empty($list)) {
11722
                foreach ($list as $item_id) {
11723
                    $item = $this->items[$item_id];
11724
                    switch ($item->type) {
11725
                        case 'document':
11726
                            // Getting documents from a LP with chamilo documents
11727
                            $file_data = DocumentManager::get_document_data_by_id($item->path, $this->cc);
11728
                            // Try loading document from the base course.
11729
                            if (empty($file_data) && !empty($sessionId)) {
11730
                                $file_data = DocumentManager::get_document_data_by_id(
11731
                                    $item->path,
11732
                                    $this->cc,
11733
                                    false,
11734
                                    0
11735
                                );
11736
                            }
11737
                            $file_path = api_get_path(SYS_COURSE_PATH).$course_data['path'].'/document'.$file_data['path'];
11738
                            if (file_exists($file_path)) {
11739
                                $files_to_export[] = [
11740
                                    'title' => $item->get_title(),
11741
                                    'path' => $file_path,
11742
                                ];
11743
                            }
11744
                            break;
11745
                        case 'asset': //commes from a scorm package generated by chamilo
11746
                        case 'sco':
11747
                            $file_path = $scorm_path.'/'.$item->path;
11748
                            if (file_exists($file_path)) {
11749
                                $files_to_export[] = [
11750
                                    'title' => $item->get_title(),
11751
                                    'path' => $file_path,
11752
                                ];
11753
                            }
11754
                            break;
11755
                        case 'dir':
11756
                            $files_to_export[] = [
11757
                                'title' => $item->get_title(),
11758
                                'path' => null,
11759
                            ];
11760
                            break;
11761
                    }
11762
                }
11763
            }
11764
11765
            $pdf = new PDF();
11766
            $result = $pdf->html_to_pdf(
11767
                $files_to_export,
11768
                $this->name,
11769
                $this->cc,
11770
                true,
11771
                true,
11772
                true,
11773
                $this->get_name()
11774
            );
11775
11776
            return $result;
11777
        }
11778
11779
        return false;
11780
    }
11781
11782
    /**
11783
     * Temp function to be moved in main_api or the best place around for this.
11784
     * Creates a file path if it doesn't exist.
11785
     *
11786
     * @param string $path
11787
     */
11788
    public function create_path($path)
11789
    {
11790
        $path_bits = explode('/', dirname($path));
11791
11792
        // IS_WINDOWS_OS has been defined in main_api.lib.php
11793
        $path_built = IS_WINDOWS_OS ? '' : '/';
11794
        foreach ($path_bits as $bit) {
11795
            if (!empty($bit)) {
11796
                $new_path = $path_built.$bit;
11797
                if (is_dir($new_path)) {
11798
                    $path_built = $new_path.'/';
11799
                } else {
11800
                    mkdir($new_path, api_get_permissions_for_new_directories());
11801
                    $path_built = $new_path.'/';
11802
                }
11803
            }
11804
        }
11805
    }
11806
11807
    /**
11808
     * Delete the image relative to this learning path. No parameter. Only works on instanciated object.
11809
     *
11810
     * @return bool The results of the unlink function, or false if there was no image to start with
11811
     */
11812
    public function delete_lp_image()
11813
    {
11814
        $img = $this->get_preview_image();
11815
        if ($img != '') {
11816
            $del_file = $this->get_preview_image_path(null, 'sys');
11817
            if (isset($del_file) && file_exists($del_file)) {
11818
                $del_file_2 = $this->get_preview_image_path(64, 'sys');
11819
                if (file_exists($del_file_2)) {
11820
                    unlink($del_file_2);
11821
                }
11822
                $this->set_preview_image('');
11823
11824
                return @unlink($del_file);
11825
            }
11826
        }
11827
11828
        return false;
11829
    }
11830
11831
    /**
11832
     * Uploads an author image to the upload/learning_path/images directory.
11833
     *
11834
     * @param array    The image array, coming from the $_FILES superglobal
11835
     *
11836
     * @return bool True on success, false on error
11837
     */
11838
    public function upload_image($image_array)
11839
    {
11840
        if (!empty($image_array['name'])) {
11841
            $upload_ok = process_uploaded_file($image_array);
11842
            $has_attachment = true;
11843
        }
11844
11845
        if ($upload_ok && $has_attachment) {
11846
            $courseDir = api_get_course_path().'/upload/learning_path/images';
11847
            $sys_course_path = api_get_path(SYS_COURSE_PATH);
11848
            $updir = $sys_course_path.$courseDir;
11849
            // Try to add an extension to the file if it hasn't one.
11850
            $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']);
11851
11852
            if (filter_extension($new_file_name)) {
11853
                $file_extension = explode('.', $image_array['name']);
11854
                $file_extension = strtolower($file_extension[count($file_extension) - 1]);
11855
                $filename = uniqid('');
11856
                $new_file_name = $filename.'.'.$file_extension;
11857
                $new_path = $updir.'/'.$new_file_name;
11858
11859
                // Resize the image.
11860
                $temp = new Image($image_array['tmp_name']);
11861
                $temp->resize(104);
11862
                $result = $temp->send_image($new_path);
11863
11864
                // Storing the image filename.
11865
                if ($result) {
11866
                    $this->set_preview_image($new_file_name);
11867
11868
                    //Resize to 64px to use on course homepage
11869
                    $temp->resize(64);
11870
                    $temp->send_image($updir.'/'.$filename.'.64.'.$file_extension);
11871
11872
                    return true;
11873
                }
11874
            }
11875
        }
11876
11877
        return false;
11878
    }
11879
11880
    /**
11881
     * @param int    $lp_id
11882
     * @param string $status
11883
     */
11884
    public function set_autolaunch($lp_id, $status)
11885
    {
11886
        $course_id = api_get_course_int_id();
11887
        $lp_id = (int) $lp_id;
11888
        $status = (int) $status;
11889
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
11890
11891
        // Setting everything to autolaunch = 0
11892
        $attributes['autolaunch'] = 0;
11893
        $where = [
11894
            'session_id = ? AND c_id = ? ' => [
11895
                api_get_session_id(),
11896
                $course_id,
11897
            ],
11898
        ];
11899
        Database::update($lp_table, $attributes, $where);
11900
        if ($status == 1) {
11901
            //Setting my lp_id to autolaunch = 1
11902
            $attributes['autolaunch'] = 1;
11903
            $where = [
11904
                'iid = ? AND session_id = ? AND c_id = ?' => [
11905
                    $lp_id,
11906
                    api_get_session_id(),
11907
                    $course_id,
11908
                ],
11909
            ];
11910
            Database::update($lp_table, $attributes, $where);
11911
        }
11912
    }
11913
11914
    /**
11915
     * Gets previous_item_id for the next element of the lp_item table.
11916
     *
11917
     * @author Isaac flores paz
11918
     *
11919
     * @return int Previous item ID
11920
     */
11921
    public function select_previous_item_id()
11922
    {
11923
        $course_id = api_get_course_int_id();
11924
        $table_lp_item = Database::get_course_table(TABLE_LP_ITEM);
11925
11926
        // Get the max order of the items
11927
        $sql = "SELECT max(display_order) AS display_order FROM $table_lp_item
11928
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
11929
        $rs_max_order = Database::query($sql);
11930
        $row_max_order = Database::fetch_object($rs_max_order);
11931
        $max_order = $row_max_order->display_order;
11932
        // Get the previous item ID
11933
        $sql = "SELECT iid as previous FROM $table_lp_item
11934
                WHERE
11935
                    c_id = $course_id AND
11936
                    lp_id = ".$this->lp_id." AND
11937
                    display_order = '$max_order' ";
11938
        $rs_max = Database::query($sql);
11939
        $row_max = Database::fetch_object($rs_max);
11940
11941
        // Return the previous item ID
11942
        return $row_max->previous;
11943
    }
11944
11945
    /**
11946
     * Copies an LP.
11947
     */
11948
    public function copy()
11949
    {
11950
        // Course builder
11951
        $cb = new CourseBuilder();
11952
11953
        //Setting tools that will be copied
11954
        $cb->set_tools_to_build(['learnpaths']);
11955
11956
        //Setting elements that will be copied
11957
        $cb->set_tools_specific_id_list(
11958
            ['learnpaths' => [$this->lp_id]]
11959
        );
11960
11961
        $course = $cb->build();
11962
11963
        //Course restorer
11964
        $course_restorer = new CourseRestorer($course);
11965
        $course_restorer->set_add_text_in_items(true);
11966
        $course_restorer->set_tool_copy_settings(
11967
            ['learnpaths' => ['reset_dates' => true]]
11968
        );
11969
        $course_restorer->restore(
11970
            api_get_course_id(),
11971
            api_get_session_id(),
11972
            false,
11973
            false
11974
        );
11975
    }
11976
11977
    /**
11978
     * Verify document size.
11979
     *
11980
     * @param string $s
11981
     *
11982
     * @return bool
11983
     */
11984
    public static function verify_document_size($s)
11985
    {
11986
        $post_max = ini_get('post_max_size');
11987
        if (substr($post_max, -1, 1) == 'M') {
11988
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024;
11989
        } elseif (substr($post_max, -1, 1) == 'G') {
11990
            $post_max = intval(substr($post_max, 0, -1)) * 1024 * 1024 * 1024;
11991
        }
11992
        $upl_max = ini_get('upload_max_filesize');
11993
        if (substr($upl_max, -1, 1) == 'M') {
11994
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024;
11995
        } elseif (substr($upl_max, -1, 1) == 'G') {
11996
            $upl_max = intval(substr($upl_max, 0, -1)) * 1024 * 1024 * 1024;
11997
        }
11998
        $documents_total_space = DocumentManager::documents_total_space();
11999
        $course_max_space = DocumentManager::get_course_quota();
12000
        $total_size = filesize($s) + $documents_total_space;
12001
        if (filesize($s) > $post_max || filesize($s) > $upl_max || $total_size > $course_max_space) {
12002
            return true;
12003
        }
12004
12005
        return false;
12006
    }
12007
12008
    /**
12009
     * Clear LP prerequisites.
12010
     */
12011
    public function clear_prerequisites()
12012
    {
12013
        $course_id = $this->get_course_int_id();
12014
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12015
        $lp_id = $this->get_id();
12016
        // Cleaning prerequisites
12017
        $sql = "UPDATE $tbl_lp_item SET prerequisite = ''
12018
                WHERE c_id = $course_id AND lp_id = $lp_id";
12019
        Database::query($sql);
12020
12021
        // Cleaning mastery score for exercises
12022
        $sql = "UPDATE $tbl_lp_item SET mastery_score = ''
12023
                WHERE c_id = $course_id AND lp_id = $lp_id AND item_type = 'quiz'";
12024
        Database::query($sql);
12025
    }
12026
12027
    public function set_previous_step_as_prerequisite_for_all_items()
12028
    {
12029
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
12030
        $course_id = $this->get_course_int_id();
12031
        $lp_id = $this->get_id();
12032
12033
        if (!empty($this->items)) {
12034
            $previous_item_id = null;
12035
            $previous_item_max = 0;
12036
            $previous_item_type = null;
12037
            $last_item_not_dir = null;
12038
            $last_item_not_dir_type = null;
12039
            $last_item_not_dir_max = null;
12040
12041
            foreach ($this->ordered_items as $itemId) {
12042
                $item = $this->getItem($itemId);
12043
                // if there was a previous item... (otherwise jump to set it)
12044
                if (!empty($previous_item_id)) {
12045
                    $current_item_id = $item->get_id(); //save current id
12046
                    if ($item->get_type() != 'dir') {
12047
                        // Current item is not a folder, so it qualifies to get a prerequisites
12048
                        if ($last_item_not_dir_type == 'quiz') {
12049
                            // if previous is quiz, mark its max score as default score to be achieved
12050
                            $sql = "UPDATE $tbl_lp_item SET mastery_score = '$last_item_not_dir_max'
12051
                                    WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $last_item_not_dir";
12052
                            Database::query($sql);
12053
                        }
12054
                        // now simply update the prerequisite to set it to the last non-chapter item
12055
                        $sql = "UPDATE $tbl_lp_item SET prerequisite = '$last_item_not_dir'
12056
                                WHERE c_id = $course_id AND lp_id = $lp_id AND iid = $current_item_id";
12057
                        Database::query($sql);
12058
                        // record item as 'non-chapter' reference
12059
                        $last_item_not_dir = $item->get_id();
12060
                        $last_item_not_dir_type = $item->get_type();
12061
                        $last_item_not_dir_max = $item->get_max();
12062
                    }
12063
                } else {
12064
                    if ($item->get_type() != 'dir') {
12065
                        // Current item is not a folder (but it is the first item) so record as last "non-chapter" item
12066
                        $last_item_not_dir = $item->get_id();
12067
                        $last_item_not_dir_type = $item->get_type();
12068
                        $last_item_not_dir_max = $item->get_max();
12069
                    }
12070
                }
12071
                // Saving the item as "previous item" for the next loop
12072
                $previous_item_id = $item->get_id();
12073
                $previous_item_max = $item->get_max();
12074
                $previous_item_type = $item->get_type();
12075
            }
12076
        }
12077
    }
12078
12079
    /**
12080
     * @param array $params
12081
     *
12082
     * @throws \Doctrine\ORM\OptimisticLockException
12083
     *
12084
     * @return int
12085
     */
12086
    public static function createCategory($params)
12087
    {
12088
        $em = Database::getManager();
12089
        $item = new CLpCategory();
12090
        $item->setName($params['name']);
12091
        $item->setCId($params['c_id']);
12092
        $em->persist($item);
12093
        $em->flush();
12094
12095
        $id = $item->getId();
12096
12097
        $sessionId = api_get_session_id();
12098
        if (!empty($sessionId) && api_get_configuration_value('allow_session_lp_category')) {
12099
            $table = Database::get_course_table(TABLE_LP_CATEGORY);
12100
            $sql = "UPDATE $table SET session_id = $sessionId WHERE iid = $id";
12101
            Database::query($sql);
12102
        }
12103
12104
        api_item_property_update(
12105
            api_get_course_info(),
12106
            TOOL_LEARNPATH_CATEGORY,
12107
            $id,
12108
            'visible',
12109
            api_get_user_id()
12110
        );
12111
12112
        return $item->getId();
12113
    }
12114
12115
    /**
12116
     * @param array $params
12117
     */
12118
    public static function updateCategory($params)
12119
    {
12120
        $em = Database::getManager();
12121
        $item = self::getCategory($params['id']);
12122
12123
        if ($item) {
12124
            $item->setName($params['name']);
12125
            $em->merge($item);
12126
            $em->flush();
12127
        }
12128
    }
12129
12130
    /**
12131
     * @param int $id
12132
     */
12133
    public static function moveUpCategory($id)
12134
    {
12135
        $item = self::getCategory($id);
12136
        if ($item) {
12137
            $em = Database::getManager();
12138
            $position = $item->getPosition() - 1;
12139
            $item->setPosition($position);
12140
            $em->persist($item);
12141
            $em->flush();
12142
        }
12143
    }
12144
12145
    /**
12146
     * @param int $id
12147
     *
12148
     * @throws \Doctrine\ORM\ORMException
12149
     * @throws \Doctrine\ORM\OptimisticLockException
12150
     * @throws \Doctrine\ORM\TransactionRequiredException
12151
     */
12152
    public static function moveDownCategory($id)
12153
    {
12154
        $item = self::getCategory($id);
12155
        if ($item) {
12156
            $em = Database::getManager();
12157
            $position = $item->getPosition() + 1;
12158
            $item->setPosition($position);
12159
            $em->persist($item);
12160
            $em->flush();
12161
        }
12162
    }
12163
12164
    public static function getLpList($courseId)
12165
    {
12166
        $table = Database::get_course_table(TABLE_LP_MAIN);
12167
        $courseId = (int) $courseId;
12168
12169
        $sql = "SELECT * FROM $table WHERE c_id = $courseId";
12170
        $result = Database::query($sql);
12171
12172
        return Database::store_result($result, 'ASSOC');
12173
    }
12174
12175
    /**
12176
     * @param int $courseId
12177
     *
12178
     * @throws \Doctrine\ORM\Query\QueryException
12179
     *
12180
     * @return int|mixed
12181
     */
12182
    public static function getCountCategories($courseId)
12183
    {
12184
        if (empty($courseId)) {
12185
            return 0;
12186
        }
12187
        $em = Database::getManager();
12188
        $query = $em->createQuery('SELECT COUNT(u.id) FROM ChamiloCourseBundle:CLpCategory u WHERE u.cId = :id');
12189
        $query->setParameter('id', $courseId);
12190
12191
        return $query->getSingleScalarResult();
12192
    }
12193
12194
    /**
12195
     * @param int $courseId
12196
     *
12197
     * @return CLpCategory[]
12198
     */
12199
    public static function getCategories($courseId)
12200
    {
12201
        $em = Database::getManager();
12202
12203
        // Using doctrine extensions
12204
        /** @var SortableRepository $repo */
12205
        $repo = $em->getRepository('ChamiloCourseBundle:CLpCategory');
12206
12207
        return $repo->getBySortableGroupsQuery(['cId' => $courseId])->getResult();
12208
    }
12209
12210
    public static function getCategorySessionId($id)
12211
    {
12212
        if (false === api_get_configuration_value('allow_session_lp_category')) {
12213
            return 0;
12214
        }
12215
12216
        $table = Database::get_course_table(TABLE_LP_CATEGORY);
12217
        $id = (int) $id;
12218
12219
        $sql = "SELECT session_id FROM $table WHERE iid = $id";
12220
        $result = Database::query($sql);
12221
        $result = Database::fetch_array($result, 'ASSOC');
12222
12223
        if ($result) {
12224
            return (int) $result['session_id'];
12225
        }
12226
12227
        return 0;
12228
    }
12229
12230
    /**
12231
     * @param int $id
12232
     *
12233
     * @return CLpCategory
12234
     */
12235
    public static function getCategory($id)
12236
    {
12237
        $id = (int) $id;
12238
        $em = Database::getManager();
12239
12240
        return $em->find('ChamiloCourseBundle:CLpCategory', $id);
12241
    }
12242
12243
    /**
12244
     * @param int $courseId
12245
     *
12246
     * @return array
12247
     */
12248
    public static function getCategoryByCourse($courseId)
12249
    {
12250
        $em = Database::getManager();
12251
12252
        return $em->getRepository('ChamiloCourseBundle:CLpCategory')->findBy(['cId' => $courseId]);
12253
    }
12254
12255
    /**
12256
     * @param int $id
12257
     *
12258
     * @throws \Doctrine\ORM\ORMException
12259
     * @throws \Doctrine\ORM\OptimisticLockException
12260
     * @throws \Doctrine\ORM\TransactionRequiredException
12261
     *
12262
     * @return mixed
12263
     */
12264
    public static function deleteCategory($id)
12265
    {
12266
        $em = Database::getManager();
12267
        $item = self::getCategory($id);
12268
        if ($item) {
12269
            $courseId = $item->getCId();
12270
            $query = $em->createQuery('SELECT u FROM ChamiloCourseBundle:CLp u WHERE u.cId = :id AND u.categoryId = :catId');
12271
            $query->setParameter('id', $courseId);
12272
            $query->setParameter('catId', $item->getId());
12273
            $lps = $query->getResult();
12274
12275
            // Setting category = 0.
12276
            if ($lps) {
12277
                foreach ($lps as $lpItem) {
12278
                    $lpItem->setCategoryId(0);
12279
                }
12280
            }
12281
12282
            // Removing category.
12283
            $em->remove($item);
12284
            $em->flush();
12285
12286
            $courseInfo = api_get_course_info_by_id($courseId);
12287
            $sessionId = api_get_session_id();
12288
12289
            // Delete link tool
12290
            $tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
12291
            $link = 'lp/lp_controller.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&gidReq=0&gradebook=0&origin=&action=view_category&id='.$id;
12292
            // Delete tools
12293
            $sql = "DELETE FROM $tbl_tool
12294
                    WHERE c_id = ".$courseId." AND (link LIKE '$link%' AND image='lp_category.gif')";
12295
            Database::query($sql);
12296
12297
            return true;
12298
        }
12299
12300
        return false;
12301
    }
12302
12303
    /**
12304
     * @param int  $courseId
12305
     * @param bool $addSelectOption
12306
     *
12307
     * @return mixed
12308
     */
12309
    public static function getCategoryFromCourseIntoSelect($courseId, $addSelectOption = false)
12310
    {
12311
        $items = self::getCategoryByCourse($courseId);
12312
        $cats = [];
12313
        if ($addSelectOption) {
12314
            $cats = [get_lang('SelectACategory')];
12315
        }
12316
12317
        if (!empty($items)) {
12318
            foreach ($items as $cat) {
12319
                $cats[$cat->getId()] = $cat->getName();
12320
            }
12321
        }
12322
12323
        return $cats;
12324
    }
12325
12326
    /**
12327
     * @param string $courseCode
12328
     * @param int    $lpId
12329
     * @param int    $user_id
12330
     *
12331
     * @return learnpath
12332
     */
12333
    public static function getLpFromSession($courseCode, $lpId, $user_id)
12334
    {
12335
        $debug = 0;
12336
        $learnPath = null;
12337
        $lpObject = Session::read('lpobject');
12338
        if ($lpObject !== null) {
12339
            $learnPath = UnserializeApi::unserialize('lp', $lpObject);
12340
            if ($debug) {
12341
                error_log('getLpFromSession: unserialize');
12342
                error_log('------getLpFromSession------');
12343
                error_log('------unserialize------');
12344
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12345
                error_log("api_get_sessionid: ".api_get_session_id());
12346
            }
12347
        }
12348
12349
        if (!is_object($learnPath)) {
12350
            $learnPath = new learnpath($courseCode, $lpId, $user_id);
12351
            if ($debug) {
12352
                error_log('------getLpFromSession------');
12353
                error_log('getLpFromSession: create new learnpath');
12354
                error_log("create new LP with $courseCode - $lpId - $user_id");
12355
                error_log("lp_view_session_id: ".$learnPath->lp_view_session_id);
12356
                error_log("api_get_sessionid: ".api_get_session_id());
12357
            }
12358
        }
12359
12360
        return $learnPath;
12361
    }
12362
12363
    /**
12364
     * @param int $itemId
12365
     *
12366
     * @return learnpathItem|false
12367
     */
12368
    public function getItem($itemId)
12369
    {
12370
        if (isset($this->items[$itemId]) && is_object($this->items[$itemId])) {
12371
            return $this->items[$itemId];
12372
        }
12373
12374
        return false;
12375
    }
12376
12377
    /**
12378
     * @return int
12379
     */
12380
    public function getCurrentAttempt()
12381
    {
12382
        $attempt = $this->getItem($this->get_current_item_id());
12383
        if ($attempt) {
12384
            $attemptId = $attempt->get_attempt_id();
12385
12386
            return $attemptId;
12387
        }
12388
12389
        return 0;
12390
    }
12391
12392
    /**
12393
     * @return int
12394
     */
12395
    public function getCategoryId()
12396
    {
12397
        return (int) $this->categoryId;
12398
    }
12399
12400
    /**
12401
     * @param int $categoryId
12402
     *
12403
     * @return bool
12404
     */
12405
    public function setCategoryId($categoryId)
12406
    {
12407
        $this->categoryId = (int) $categoryId;
12408
        $table = Database::get_course_table(TABLE_LP_MAIN);
12409
        $lp_id = $this->get_id();
12410
        $sql = "UPDATE $table SET category_id = ".$this->categoryId."
12411
                WHERE iid = $lp_id";
12412
        Database::query($sql);
12413
12414
        return true;
12415
    }
12416
12417
    /**
12418
     * Get whether this is a learning path with the possibility to subscribe
12419
     * users or not.
12420
     *
12421
     * @return int
12422
     */
12423
    public function getSubscribeUsers()
12424
    {
12425
        return $this->subscribeUsers;
12426
    }
12427
12428
    /**
12429
     * Set whether this is a learning path with the possibility to subscribe
12430
     * users or not.
12431
     *
12432
     * @param int $value (0 = false, 1 = true)
12433
     *
12434
     * @return bool
12435
     */
12436
    public function setSubscribeUsers($value)
12437
    {
12438
        $this->subscribeUsers = (int) $value;
12439
        $table = Database::get_course_table(TABLE_LP_MAIN);
12440
        $lp_id = $this->get_id();
12441
        $sql = "UPDATE $table SET subscribe_users = ".$this->subscribeUsers."
12442
                WHERE iid = $lp_id";
12443
        Database::query($sql);
12444
12445
        return true;
12446
    }
12447
12448
    /**
12449
     * Calculate the count of stars for a user in this LP
12450
     * This calculation is based on the following rules:
12451
     * - the student gets one star when he gets to 50% of the learning path
12452
     * - the student gets a second star when the average score of all tests inside the learning path >= 50%
12453
     * - the student gets a third star when the average score of all tests inside the learning path >= 80%
12454
     * - the student gets the final star when the score for the *last* test is >= 80%.
12455
     *
12456
     * @param int $sessionId Optional. The session ID
12457
     *
12458
     * @return int The count of stars
12459
     */
12460
    public function getCalculateStars($sessionId = 0)
12461
    {
12462
        $stars = 0;
12463
        $progress = self::getProgress(
12464
            $this->lp_id,
12465
            $this->user_id,
12466
            $this->course_int_id,
12467
            $sessionId
12468
        );
12469
12470
        if ($progress >= 50) {
12471
            $stars++;
12472
        }
12473
12474
        // Calculate stars chapters evaluation
12475
        $exercisesItems = $this->getExercisesItems();
12476
12477
        if (!empty($exercisesItems)) {
12478
            $totalResult = 0;
12479
12480
            foreach ($exercisesItems as $exerciseItem) {
12481
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12482
                    $this->user_id,
12483
                    $exerciseItem->path,
12484
                    $this->course_int_id,
12485
                    $sessionId,
12486
                    $this->lp_id,
12487
                    $exerciseItem->db_id
12488
                );
12489
12490
                $exerciseResultInfo = end($exerciseResultInfo);
12491
12492
                if (!$exerciseResultInfo) {
12493
                    continue;
12494
                }
12495
12496
                if (!empty($exerciseResultInfo['exe_weighting'])) {
12497
                    $exerciseResult = $exerciseResultInfo['exe_result'] * 100 / $exerciseResultInfo['exe_weighting'];
12498
                } else {
12499
                    $exerciseResult = 0;
12500
                }
12501
                $totalResult += $exerciseResult;
12502
            }
12503
12504
            $totalExerciseAverage = $totalResult / (count($exercisesItems) > 0 ? count($exercisesItems) : 1);
12505
12506
            if ($totalExerciseAverage >= 50) {
12507
                $stars++;
12508
            }
12509
12510
            if ($totalExerciseAverage >= 80) {
12511
                $stars++;
12512
            }
12513
        }
12514
12515
        // Calculate star for final evaluation
12516
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12517
12518
        if (!empty($finalEvaluationItem)) {
12519
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12520
                $this->user_id,
12521
                $finalEvaluationItem->path,
12522
                $this->course_int_id,
12523
                $sessionId,
12524
                $this->lp_id,
12525
                $finalEvaluationItem->db_id
12526
            );
12527
12528
            $evaluationResultInfo = end($evaluationResultInfo);
12529
12530
            if ($evaluationResultInfo) {
12531
                $evaluationResult = $evaluationResultInfo['exe_result'] * 100 / $evaluationResultInfo['exe_weighting'];
12532
12533
                if ($evaluationResult >= 80) {
12534
                    $stars++;
12535
                }
12536
            }
12537
        }
12538
12539
        return $stars;
12540
    }
12541
12542
    /**
12543
     * Get the items of exercise type.
12544
     *
12545
     * @return array The items. Otherwise return false
12546
     */
12547
    public function getExercisesItems()
12548
    {
12549
        $exercises = [];
12550
        foreach ($this->items as $item) {
12551
            if ($item->type != 'quiz') {
12552
                continue;
12553
            }
12554
            $exercises[] = $item;
12555
        }
12556
12557
        array_pop($exercises);
12558
12559
        return $exercises;
12560
    }
12561
12562
    /**
12563
     * Get the item of exercise type (evaluation type).
12564
     *
12565
     * @return array The final evaluation. Otherwise return false
12566
     */
12567
    public function getFinalEvaluationItem()
12568
    {
12569
        $exercises = [];
12570
        foreach ($this->items as $item) {
12571
            if ($item->type != 'quiz') {
12572
                continue;
12573
            }
12574
12575
            $exercises[] = $item;
12576
        }
12577
12578
        return array_pop($exercises);
12579
    }
12580
12581
    /**
12582
     * Calculate the total points achieved for the current user in this learning path.
12583
     *
12584
     * @param int $sessionId Optional. The session Id
12585
     *
12586
     * @return int
12587
     */
12588
    public function getCalculateScore($sessionId = 0)
12589
    {
12590
        // Calculate stars chapters evaluation
12591
        $exercisesItems = $this->getExercisesItems();
12592
        $finalEvaluationItem = $this->getFinalEvaluationItem();
12593
        $totalExercisesResult = 0;
12594
        $totalEvaluationResult = 0;
12595
12596
        if ($exercisesItems !== false) {
12597
            foreach ($exercisesItems as $exerciseItem) {
12598
                $exerciseResultInfo = Event::getExerciseResultsByUser(
12599
                    $this->user_id,
12600
                    $exerciseItem->path,
12601
                    $this->course_int_id,
12602
                    $sessionId,
12603
                    $this->lp_id,
12604
                    $exerciseItem->db_id
12605
                );
12606
12607
                $exerciseResultInfo = end($exerciseResultInfo);
12608
12609
                if (!$exerciseResultInfo) {
12610
                    continue;
12611
                }
12612
12613
                $totalExercisesResult += $exerciseResultInfo['exe_result'];
12614
            }
12615
        }
12616
12617
        if (!empty($finalEvaluationItem)) {
12618
            $evaluationResultInfo = Event::getExerciseResultsByUser(
12619
                $this->user_id,
12620
                $finalEvaluationItem->path,
12621
                $this->course_int_id,
12622
                $sessionId,
12623
                $this->lp_id,
12624
                $finalEvaluationItem->db_id
12625
            );
12626
12627
            $evaluationResultInfo = end($evaluationResultInfo);
12628
12629
            if ($evaluationResultInfo) {
12630
                $totalEvaluationResult += $evaluationResultInfo['exe_result'];
12631
            }
12632
        }
12633
12634
        return $totalExercisesResult + $totalEvaluationResult;
12635
    }
12636
12637
    /**
12638
     * Check if URL is not allowed to be show in a iframe.
12639
     *
12640
     * @param string $src
12641
     *
12642
     * @return string
12643
     */
12644
    public function fixBlockedLinks($src)
12645
    {
12646
        $urlInfo = parse_url($src);
12647
12648
        $platformProtocol = 'https';
12649
        if (strpos(api_get_path(WEB_CODE_PATH), 'https') === false) {
12650
            $platformProtocol = 'http';
12651
        }
12652
12653
        $protocolFixApplied = false;
12654
        //Scheme validation to avoid "Notices" when the lesson doesn't contain a valid scheme
12655
        $scheme = isset($urlInfo['scheme']) ? $urlInfo['scheme'] : null;
12656
        $host = isset($urlInfo['host']) ? $urlInfo['host'] : null;
12657
12658
        if ($platformProtocol != $scheme) {
12659
            Session::write('x_frame_source', $src);
12660
            $src = 'blank.php?error=x_frames_options';
12661
            $protocolFixApplied = true;
12662
        }
12663
12664
        if ($protocolFixApplied == false) {
12665
            if (strpos(api_get_path(WEB_PATH), $host) === false) {
12666
                // Check X-Frame-Options
12667
                $ch = curl_init();
12668
                $options = [
12669
                    CURLOPT_URL => $src,
12670
                    CURLOPT_RETURNTRANSFER => true,
12671
                    CURLOPT_HEADER => true,
12672
                    CURLOPT_FOLLOWLOCATION => true,
12673
                    CURLOPT_ENCODING => "",
12674
                    CURLOPT_AUTOREFERER => true,
12675
                    CURLOPT_CONNECTTIMEOUT => 120,
12676
                    CURLOPT_TIMEOUT => 120,
12677
                    CURLOPT_MAXREDIRS => 10,
12678
                ];
12679
12680
                $proxySettings = api_get_configuration_value('proxy_settings');
12681
                if (!empty($proxySettings) &&
12682
                    isset($proxySettings['curl_setopt_array'])
12683
                ) {
12684
                    $options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
12685
                    $options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
12686
                }
12687
12688
                curl_setopt_array($ch, $options);
12689
                $response = curl_exec($ch);
12690
                $httpCode = curl_getinfo($ch);
12691
                $headers = substr($response, 0, $httpCode['header_size']);
12692
12693
                $error = false;
12694
                if (stripos($headers, 'X-Frame-Options: DENY') > -1
12695
                    //|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
12696
                ) {
12697
                    $error = true;
12698
                }
12699
12700
                if ($error) {
12701
                    Session::write('x_frame_source', $src);
12702
                    $src = 'blank.php?error=x_frames_options';
12703
                }
12704
            }
12705
        }
12706
12707
        return $src;
12708
    }
12709
12710
    /**
12711
     * Check if this LP has a created forum in the basis course.
12712
     *
12713
     * @return bool
12714
     */
12715
    public function lpHasForum()
12716
    {
12717
        $forumTable = Database::get_course_table(TABLE_FORUM);
12718
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12719
12720
        $fakeFrom = "
12721
            $forumTable f
12722
            INNER JOIN $itemProperty ip
12723
            ON (f.forum_id = ip.ref AND f.c_id = ip.c_id)
12724
        ";
12725
12726
        $resultData = Database::select(
12727
            'COUNT(f.iid) AS qty',
12728
            $fakeFrom,
12729
            [
12730
                'where' => [
12731
                    'ip.visibility != ? AND ' => 2,
12732
                    'ip.tool = ? AND ' => TOOL_FORUM,
12733
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12734
                    'f.lp_id = ?' => intval($this->lp_id),
12735
                ],
12736
            ],
12737
            'first'
12738
        );
12739
12740
        return $resultData['qty'] > 0;
12741
    }
12742
12743
    /**
12744
     * Get the forum for this learning path.
12745
     *
12746
     * @param int $sessionId
12747
     *
12748
     * @return bool
12749
     */
12750
    public function getForum($sessionId = 0)
12751
    {
12752
        $forumTable = Database::get_course_table(TABLE_FORUM);
12753
        $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
12754
12755
        $fakeFrom = "$forumTable f
12756
            INNER JOIN $itemProperty ip ";
12757
12758
        if ($this->lp_session_id == 0) {
12759
            $fakeFrom .= "
12760
                ON (
12761
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND (
12762
                        f.session_id = ip.session_id OR ip.session_id IS NULL
12763
                    )
12764
                )
12765
            ";
12766
        } else {
12767
            $fakeFrom .= "
12768
                ON (
12769
                    f.forum_id = ip.ref AND f.c_id = ip.c_id AND f.session_id = ip.session_id
12770
                )
12771
            ";
12772
        }
12773
12774
        $resultData = Database::select(
12775
            'f.*',
12776
            $fakeFrom,
12777
            [
12778
                'where' => [
12779
                    'ip.visibility != ? AND ' => 2,
12780
                    'ip.tool = ? AND ' => TOOL_FORUM,
12781
                    'f.session_id = ? AND ' => $sessionId,
12782
                    'f.c_id = ? AND ' => intval($this->course_int_id),
12783
                    'f.lp_id = ?' => intval($this->lp_id),
12784
                ],
12785
            ],
12786
            'first'
12787
        );
12788
12789
        if (empty($resultData)) {
12790
            return false;
12791
        }
12792
12793
        return $resultData;
12794
    }
12795
12796
    /**
12797
     * Create a forum for this learning path.
12798
     *
12799
     * @param int $forumCategoryId
12800
     *
12801
     * @return int The forum ID if was created. Otherwise return false
12802
     */
12803
    public function createForum($forumCategoryId)
12804
    {
12805
        require_once api_get_path(SYS_CODE_PATH).'/forum/forumfunction.inc.php';
12806
12807
        $forumId = store_forum(
12808
            [
12809
                'lp_id' => $this->lp_id,
12810
                'forum_title' => $this->name,
12811
                'forum_comment' => null,
12812
                'forum_category' => (int) $forumCategoryId,
12813
                'students_can_edit_group' => ['students_can_edit' => 0],
12814
                'allow_new_threads_group' => ['allow_new_threads' => 0],
12815
                'default_view_type_group' => ['default_view_type' => 'flat'],
12816
                'group_forum' => 0,
12817
                'public_private_group_forum_group' => ['public_private_group_forum' => 'public'],
12818
            ],
12819
            [],
12820
            true
12821
        );
12822
12823
        return $forumId;
12824
    }
12825
12826
    /**
12827
     * Get the LP Final Item form.
12828
     *
12829
     * @throws Exception
12830
     * @throws HTML_QuickForm_Error
12831
     *
12832
     * @return string
12833
     */
12834
    public function getFinalItemForm()
12835
    {
12836
        $finalItem = $this->getFinalItem();
12837
        $title = '';
12838
12839
        if ($finalItem) {
12840
            $title = $finalItem->get_title();
12841
            $buttonText = get_lang('Save');
12842
            $content = $this->getSavedFinalItem();
12843
        } else {
12844
            $buttonText = get_lang('LPCreateDocument');
12845
            $content = $this->getFinalItemTemplate();
12846
        }
12847
12848
        $courseInfo = api_get_course_info();
12849
        $result = $this->generate_lp_folder($courseInfo);
12850
        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
12851
        $relative_prefix = '../../';
12852
12853
        $editorConfig = [
12854
            'ToolbarSet' => 'LearningPathDocuments',
12855
            'Width' => '100%',
12856
            'Height' => '500',
12857
            'FullPage' => true,
12858
            'CreateDocumentDir' => $relative_prefix,
12859
            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/',
12860
            'BaseHref' => api_get_path(WEB_COURSE_PATH).api_get_course_path().'/document/'.$relative_path,
12861
        ];
12862
12863
        $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query([
12864
            'type' => 'document',
12865
            'lp_id' => $this->lp_id,
12866
        ]);
12867
12868
        $form = new FormValidator('final_item', 'POST', $url);
12869
        $form->addText('title', get_lang('Title'));
12870
        $form->addButtonSave($buttonText);
12871
        $form->addHtml(
12872
            Display::return_message(
12873
                'Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b>',
12874
                'normal',
12875
                false
12876
            )
12877
        );
12878
12879
        $renderer = $form->defaultRenderer();
12880
        $renderer->setElementTemplate('&nbsp;{label}{element}', 'content_lp_certificate');
12881
12882
        $form->addHtmlEditor(
12883
            'content_lp_certificate',
12884
            null,
12885
            true,
12886
            false,
12887
            $editorConfig,
12888
            true
12889
        );
12890
        $form->addHidden('action', 'add_final_item');
12891
        $form->addHidden('path', Session::read('pathItem'));
12892
        $form->addHidden('previous', $this->get_last());
12893
        $form->setDefaults(
12894
            ['title' => $title, 'content_lp_certificate' => $content]
12895
        );
12896
12897
        if ($form->validate()) {
12898
            $values = $form->exportValues();
12899
            $lastItemId = $this->getLastInFirstLevel();
12900
12901
            if (!$finalItem) {
12902
                $documentId = $this->create_document(
12903
                    $this->course_info,
12904
                    $values['content_lp_certificate'],
12905
                    $values['title']
12906
                );
12907
                $this->add_item(
12908
                    0,
12909
                    $lastItemId,
12910
                    'final_item',
12911
                    $documentId,
12912
                    $values['title'],
12913
                    ''
12914
                );
12915
12916
                Display::addFlash(
12917
                    Display::return_message(get_lang('Added'))
12918
                );
12919
            } else {
12920
                $this->edit_document($this->course_info);
12921
            }
12922
        }
12923
12924
        return $form->returnForm();
12925
    }
12926
12927
    /**
12928
     * Check if the current lp item is first, both, last or none from lp list.
12929
     *
12930
     * @param int $currentItemId
12931
     *
12932
     * @return string
12933
     */
12934
    public function isFirstOrLastItem($currentItemId)
12935
    {
12936
        $lpItemId = [];
12937
        $typeListNotToVerify = self::getChapterTypes();
12938
12939
        // Using get_toc() function instead $this->items because returns the correct order of the items
12940
        foreach ($this->get_toc() as $item) {
12941
            if (!in_array($item['type'], $typeListNotToVerify)) {
12942
                $lpItemId[] = $item['id'];
12943
            }
12944
        }
12945
12946
        $lastLpItemIndex = count($lpItemId) - 1;
12947
        $position = array_search($currentItemId, $lpItemId);
12948
12949
        switch ($position) {
12950
            case 0:
12951
                if (!$lastLpItemIndex) {
12952
                    $answer = 'both';
12953
                    break;
12954
                }
12955
12956
                $answer = 'first';
12957
                break;
12958
            case $lastLpItemIndex:
12959
                $answer = 'last';
12960
                break;
12961
            default:
12962
                $answer = 'none';
12963
        }
12964
12965
        return $answer;
12966
    }
12967
12968
    /**
12969
     * Get whether this is a learning path with the accumulated SCORM time or not.
12970
     *
12971
     * @return int
12972
     */
12973
    public function getAccumulateScormTime()
12974
    {
12975
        return $this->accumulateScormTime;
12976
    }
12977
12978
    /**
12979
     * Set whether this is a learning path with the accumulated SCORM time or not.
12980
     *
12981
     * @param int $value (0 = false, 1 = true)
12982
     *
12983
     * @return bool Always returns true
12984
     */
12985
    public function setAccumulateScormTime($value)
12986
    {
12987
        $this->accumulateScormTime = (int) $value;
12988
        $lp_table = Database::get_course_table(TABLE_LP_MAIN);
12989
        $lp_id = $this->get_id();
12990
        $sql = "UPDATE $lp_table
12991
                SET accumulate_scorm_time = ".$this->accumulateScormTime."
12992
                WHERE iid = $lp_id";
12993
        Database::query($sql);
12994
12995
        return true;
12996
    }
12997
12998
    /**
12999
     * Returns an HTML-formatted link to a resource, to incorporate directly into
13000
     * the new learning path tool.
13001
     *
13002
     * The function is a big switch on tool type.
13003
     * In each case, we query the corresponding table for information and build the link
13004
     * with that information.
13005
     *
13006
     * @author Yannick Warnier <[email protected]> - rebranding based on
13007
     * previous work (display_addedresource_link_in_learnpath())
13008
     *
13009
     * @param int $course_id      Course code
13010
     * @param int $learningPathId The learning path ID (in lp table)
13011
     * @param int $id_in_path     the unique index in the items table
13012
     * @param int $lpViewId
13013
     * @param int $lpSessionId
13014
     *
13015
     * @return string
13016
     */
13017
    public static function rl_get_resource_link_for_learnpath(
13018
        $course_id,
13019
        $learningPathId,
13020
        $id_in_path,
13021
        $lpViewId,
13022
        $lpSessionId = 0
13023
    ) {
13024
        $session_id = api_get_session_id();
13025
        $course_info = api_get_course_info_by_id($course_id);
13026
13027
        $learningPathId = (int) $learningPathId;
13028
        $id_in_path = (int) $id_in_path;
13029
        $lpViewId = (int) $lpViewId;
13030
13031
        $em = Database::getManager();
13032
        $lpItemRepo = $em->getRepository('ChamiloCourseBundle:CLpItem');
13033
13034
        /** @var CLpItem $rowItem */
13035
        $rowItem = $lpItemRepo->findOneBy([
13036
            'cId' => $course_id,
13037
            'lpId' => $learningPathId,
13038
            'iid' => $id_in_path,
13039
        ]);
13040
13041
        if (!$rowItem) {
13042
            // Try one more time with "id"
13043
            /** @var CLpItem $rowItem */
13044
            $rowItem = $lpItemRepo->findOneBy([
13045
                'cId' => $course_id,
13046
                'lpId' => $learningPathId,
13047
                'id' => $id_in_path,
13048
            ]);
13049
13050
            if (!$rowItem) {
13051
                return -1;
13052
            }
13053
        }
13054
13055
        $course_code = $course_info['code'];
13056
        $type = $rowItem->getItemType();
13057
        $id = empty($rowItem->getPath()) ? '0' : $rowItem->getPath();
13058
        $main_dir_path = api_get_path(WEB_CODE_PATH);
13059
        $main_course_path = api_get_path(WEB_COURSE_PATH).$course_info['directory'].'/';
13060
        $link = '';
13061
        $extraParams = api_get_cidreq(true, true, 'learnpath').'&session_id='.$session_id;
13062
13063
        switch ($type) {
13064
            case 'dir':
13065
                return $main_dir_path.'lp/blank.php';
13066
            case TOOL_CALENDAR_EVENT:
13067
                return $main_dir_path.'calendar/agenda.php?agenda_id='.$id.'&'.$extraParams;
13068
            case TOOL_ANNOUNCEMENT:
13069
                return $main_dir_path.'announcements/announcements.php?ann_id='.$id.'&'.$extraParams;
13070
            case TOOL_LINK:
13071
                $linkInfo = Link::getLinkInfo($id);
13072
                if (isset($linkInfo['url'])) {
13073
                    return $linkInfo['url'];
13074
                }
13075
13076
                return '';
13077
            case TOOL_QUIZ:
13078
                if (empty($id)) {
13079
                    return '';
13080
                }
13081
13082
                // Get the lp_item_view with the highest view_count.
13083
                $learnpathItemViewResult = $em
13084
                    ->getRepository('ChamiloCourseBundle:CLpItemView')
13085
                    ->findBy(
13086
                        ['cId' => $course_id, 'lpItemId' => $rowItem->getId(), 'lpViewId' => $lpViewId],
13087
                        ['viewCount' => 'DESC'],
13088
                        1
13089
                    );
13090
                /** @var CLpItemView $learnpathItemViewData */
13091
                $learnpathItemViewData = current($learnpathItemViewResult);
13092
                $learnpathItemViewId = $learnpathItemViewData ? $learnpathItemViewData->getId() : 0;
13093
13094
                return $main_dir_path.'exercise/overview.php?'.$extraParams.'&'
13095
                    .http_build_query([
13096
                        'lp_init' => 1,
13097
                        'learnpath_item_view_id' => $learnpathItemViewId,
13098
                        'learnpath_id' => $learningPathId,
13099
                        'learnpath_item_id' => $id_in_path,
13100
                        'exerciseId' => $id,
13101
                    ]);
13102
            case TOOL_HOTPOTATOES: //lowercase because of strtolower above
13103
                $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
13104
                $result = Database::query("SELECT * FROM ".$TBL_DOCUMENT." WHERE c_id = $course_id AND iid=$id");
13105
                $myrow = Database::fetch_array($result);
13106
                $path = $myrow['path'];
13107
13108
                return $main_dir_path.'exercise/showinframes.php?file='.$path.'&cid='.$course_code.'&uid='
13109
                    .api_get_user_id().'&learnpath_id='.$learningPathId.'&learnpath_item_id='.$id_in_path
13110
                    .'&lp_view_id='.$lpViewId.'&'.$extraParams;
13111
            case TOOL_FORUM:
13112
                return $main_dir_path.'forum/viewforum.php?forum='.$id.'&lp=true&'.$extraParams;
13113
            case TOOL_THREAD:
13114
                // forum post
13115
                $tbl_topics = Database::get_course_table(TABLE_FORUM_THREAD);
13116
                if (empty($id)) {
13117
                    return '';
13118
                }
13119
                $sql = "SELECT * FROM $tbl_topics WHERE c_id = $course_id AND thread_id=$id";
13120
                $result = Database::query($sql);
13121
                $myrow = Database::fetch_array($result);
13122
13123
                return $main_dir_path.'forum/viewthread.php?thread='.$id.'&forum='.$myrow['forum_id'].'&lp=true&'
13124
                    .$extraParams;
13125
            case TOOL_POST:
13126
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13127
                $result = Database::query("SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=$id");
13128
                $myrow = Database::fetch_array($result);
13129
13130
                return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
13131
                    .$myrow['forum_id'].'&lp=true&'.$extraParams;
13132
            case TOOL_READOUT_TEXT:
13133
                return api_get_path(WEB_CODE_PATH).
13134
                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
13135
            case TOOL_DOCUMENT:
13136
                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
13137
                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
13138
13139
                if (empty($document)) {
13140
                    // Try with normal id
13141
                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
13142
13143
                    if (empty($document)) {
13144
                        return '';
13145
                    }
13146
                }
13147
13148
                $documentPathInfo = pathinfo($document->getPath());
13149
                $mediaSupportedFiles = ['mp3', 'mp4', 'ogv', 'ogg', 'flv', 'm4v'];
13150
                $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
13151
                $showDirectUrl = !in_array($extension, $mediaSupportedFiles);
13152
13153
                $openmethod = 2;
13154
                $officedoc = false;
13155
                Session::write('openmethod', $openmethod);
13156
                Session::write('officedoc', $officedoc);
13157
13158
                if ($showDirectUrl) {
13159
                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
13160
                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
13161
                        if (Link::isPdfLink($file)) {
13162
                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
13163
13164
                            return $pdfUrl;
13165
                        }
13166
                    }
13167
13168
                    return $file;
13169
                }
13170
13171
                return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
13172
            case TOOL_LP_FINAL_ITEM:
13173
                return api_get_path(WEB_CODE_PATH).'lp/lp_final_item.php?&id='.$id.'&lp_id='.$learningPathId.'&'
13174
                    .$extraParams;
13175
            case 'assignments':
13176
                return $main_dir_path.'work/work.php?'.$extraParams;
13177
            case TOOL_DROPBOX:
13178
                return $main_dir_path.'dropbox/index.php?'.$extraParams;
13179
            case 'introduction_text': //DEPRECATED
13180
                return '';
13181
            case TOOL_COURSE_DESCRIPTION:
13182
                return $main_dir_path.'course_description?'.$extraParams;
13183
            case TOOL_GROUP:
13184
                return $main_dir_path.'group/group.php?'.$extraParams;
13185
            case TOOL_USER:
13186
                return $main_dir_path.'user/user.php?'.$extraParams;
13187
            case TOOL_STUDENTPUBLICATION:
13188
                if (!empty($rowItem->getPath())) {
13189
                    $workId = $rowItem->getPath();
13190
                    if (empty($lpSessionId) && !empty($session_id)) {
13191
                        // Check if a student publication with the same name exists in this session see BT#17700
13192
                        $title = Database::escape_string($rowItem->getTitle());
13193
                        $table = Database::get_course_table(TABLE_STUDENT_PUBLICATION);
13194
                        $sql = "SELECT * FROM $table
13195
                                WHERE
13196
                                    active = 1 AND
13197
                                    parent_id = 0 AND
13198
                                    c_id = $course_id AND
13199
                                    session_id = $session_id AND
13200
                                    title = '$title'
13201
                                LIMIT 1";
13202
                        $result = Database::query($sql);
13203
                        if (Database::num_rows($result)) {
13204
                            $work = Database::fetch_array($result, 'ASSOC');
13205
                            if ($work) {
13206
                                $workId = $work['iid'];
13207
                            }
13208
                        }
13209
                    }
13210
13211
                    return $main_dir_path.'work/work_list.php?id='.$workId.'&'.$extraParams;
13212
                }
13213
13214
                return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
13215
        }
13216
13217
        return $link;
13218
    }
13219
13220
    /**
13221
     * Gets the name of a resource (generally used in learnpath when no name is provided).
13222
     *
13223
     * @author Yannick Warnier <[email protected]>
13224
     *
13225
     * @param string $course_code    Course code
13226
     * @param int    $learningPathId
13227
     * @param int    $id_in_path     The resource ID
13228
     *
13229
     * @return string
13230
     */
13231
    public static function rl_get_resource_name($course_code, $learningPathId, $id_in_path)
13232
    {
13233
        $_course = api_get_course_info($course_code);
13234
        if (empty($_course)) {
13235
            return '';
13236
        }
13237
        $course_id = $_course['real_id'];
13238
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13239
        $learningPathId = (int) $learningPathId;
13240
        $id_in_path = (int) $id_in_path;
13241
13242
        $sql = "SELECT item_type, title, ref
13243
                FROM $tbl_lp_item
13244
                WHERE c_id = $course_id AND lp_id = $learningPathId AND iid = $id_in_path";
13245
        $res_item = Database::query($sql);
13246
13247
        if (Database::num_rows($res_item) < 1) {
13248
            return '';
13249
        }
13250
        $row_item = Database::fetch_array($res_item);
13251
        $type = strtolower($row_item['item_type']);
13252
        $id = $row_item['ref'];
13253
        $output = '';
13254
13255
        switch ($type) {
13256
            case TOOL_CALENDAR_EVENT:
13257
                $TABLEAGENDA = Database::get_course_table(TABLE_AGENDA);
13258
                $result = Database::query("SELECT * FROM $TABLEAGENDA WHERE c_id = $course_id AND id=$id");
13259
                $myrow = Database::fetch_array($result);
13260
                $output = $myrow['title'];
13261
                break;
13262
            case TOOL_ANNOUNCEMENT:
13263
                $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
13264
                $result = Database::query("SELECT * FROM $tbl_announcement WHERE c_id = $course_id AND id=$id");
13265
                $myrow = Database::fetch_array($result);
13266
                $output = $myrow['title'];
13267
                break;
13268
            case TOOL_LINK:
13269
                // Doesn't take $target into account.
13270
                $TABLETOOLLINK = Database::get_course_table(TABLE_LINK);
13271
                $result = Database::query("SELECT * FROM $TABLETOOLLINK WHERE c_id = $course_id AND id=$id");
13272
                $myrow = Database::fetch_array($result);
13273
                $output = $myrow['title'];
13274
                break;
13275
            case TOOL_QUIZ:
13276
                $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
13277
                $result = Database::query("SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND id = $id");
13278
                $myrow = Database::fetch_array($result);
13279
                $output = $myrow['title'];
13280
                break;
13281
            case TOOL_FORUM:
13282
                $TBL_FORUMS = Database::get_course_table(TABLE_FORUM);
13283
                $result = Database::query("SELECT * FROM $TBL_FORUMS WHERE c_id = $course_id AND forum_id = $id");
13284
                $myrow = Database::fetch_array($result);
13285
                $output = $myrow['forum_name'];
13286
                break;
13287
            case TOOL_THREAD:
13288
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13289
                // Grabbing the title of the post.
13290
                $sql_title = "SELECT * FROM $tbl_post WHERE c_id = $course_id AND post_id=".$id;
13291
                $result_title = Database::query($sql_title);
13292
                $myrow_title = Database::fetch_array($result_title);
13293
                $output = $myrow_title['post_title'];
13294
                break;
13295
            case TOOL_POST:
13296
                $tbl_post = Database::get_course_table(TABLE_FORUM_POST);
13297
                $sql = "SELECT * FROM $tbl_post p WHERE c_id = $course_id AND p.post_id = $id";
13298
                $result = Database::query($sql);
13299
                $post = Database::fetch_array($result);
13300
                $output = $post['post_title'];
13301
                break;
13302
            case 'dir':
13303
            case TOOL_DOCUMENT:
13304
                $title = $row_item['title'];
13305
                $output = '-';
13306
                if (!empty($title)) {
13307
                    $output = $title;
13308
                }
13309
                break;
13310
            case 'hotpotatoes':
13311
                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
13312
                $result = Database::query("SELECT * FROM $tbl_doc WHERE c_id = $course_id AND iid = $id");
13313
                $myrow = Database::fetch_array($result);
13314
                $pathname = explode('/', $myrow['path']); // Making a correct name for the link.
13315
                $last = count($pathname) - 1; // Making a correct name for the link.
13316
                $filename = $pathname[$last]; // Making a correct name for the link.
13317
                $myrow['path'] = rawurlencode($myrow['path']);
13318
                $output = $filename;
13319
                break;
13320
        }
13321
13322
        return stripslashes($output);
13323
    }
13324
13325
    /**
13326
     * Get the parent names for the current item.
13327
     *
13328
     * @param int $newItemId Optional. The item ID
13329
     *
13330
     * @return array
13331
     */
13332
    public function getCurrentItemParentNames($newItemId = 0)
13333
    {
13334
        $newItemId = $newItemId ?: $this->get_current_item_id();
13335
        $return = [];
13336
        $item = $this->getItem($newItemId);
13337
        $parent = $this->getItem($item->get_parent());
13338
13339
        while ($parent) {
13340
            $return[] = $parent->get_title();
13341
            $parent = $this->getItem($parent->get_parent());
13342
        }
13343
13344
        return array_reverse($return);
13345
    }
13346
13347
    /**
13348
     * Reads and process "lp_subscription_settings" setting.
13349
     *
13350
     * @return array
13351
     */
13352
    public static function getSubscriptionSettings()
13353
    {
13354
        $subscriptionSettings = api_get_configuration_value('lp_subscription_settings');
13355
        if (empty($subscriptionSettings)) {
13356
            // By default allow both settings
13357
            $subscriptionSettings = [
13358
                'allow_add_users_to_lp' => true,
13359
                'allow_add_users_to_lp_category' => true,
13360
            ];
13361
        } else {
13362
            $subscriptionSettings = $subscriptionSettings['options'];
13363
        }
13364
13365
        return $subscriptionSettings;
13366
    }
13367
13368
    /**
13369
     * Exports a LP to a courseBuilder zip file. It adds the documents related to the LP.
13370
     */
13371
    public function exportToCourseBuildFormat()
13372
    {
13373
        if (!api_is_allowed_to_edit()) {
13374
            return false;
13375
        }
13376
13377
        $courseBuilder = new CourseBuilder();
13378
        $itemList = [];
13379
        /** @var learnpathItem $item */
13380
        foreach ($this->items as $item) {
13381
            $itemList[$item->get_type()][] = $item->get_path();
13382
        }
13383
13384
        if (empty($itemList)) {
13385
            return false;
13386
        }
13387
13388
        if (isset($itemList['document'])) {
13389
            // Get parents
13390
            foreach ($itemList['document'] as $documentId) {
13391
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), true);
13392
                if (!empty($documentInfo['parents'])) {
13393
                    foreach ($documentInfo['parents'] as $parentInfo) {
13394
                        if (in_array($parentInfo['iid'], $itemList['document'])) {
13395
                            continue;
13396
                        }
13397
                        $itemList['document'][] = $parentInfo['iid'];
13398
                    }
13399
                }
13400
            }
13401
13402
            $courseInfo = api_get_course_info();
13403
            foreach ($itemList['document'] as $documentId) {
13404
                $documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id());
13405
                $items = DocumentManager::get_resources_from_source_html(
13406
                    $documentInfo['absolute_path'],
13407
                    true,
13408
                    TOOL_DOCUMENT
13409
                );
13410
13411
                if (!empty($items)) {
13412
                    foreach ($items as $item) {
13413
                        // Get information about source url
13414
                        $url = $item[0]; // url
13415
                        $scope = $item[1]; // scope (local, remote)
13416
                        $type = $item[2]; // type (rel, abs, url)
13417
13418
                        $origParseUrl = parse_url($url);
13419
                        $realOrigPath = isset($origParseUrl['path']) ? $origParseUrl['path'] : null;
13420
13421
                        if ($scope == 'local') {
13422
                            if ($type == 'abs' || $type == 'rel') {
13423
                                $documentFile = strstr($realOrigPath, 'document');
13424
                                if (strpos($realOrigPath, $documentFile) !== false) {
13425
                                    $documentFile = str_replace('document', '', $documentFile);
13426
                                    $itemDocumentId = DocumentManager::get_document_id($courseInfo, $documentFile);
13427
                                    // Document found! Add it to the list
13428
                                    if ($itemDocumentId) {
13429
                                        $itemList['document'][] = $itemDocumentId;
13430
                                    }
13431
                                }
13432
                            }
13433
                        }
13434
                    }
13435
                }
13436
            }
13437
13438
            $courseBuilder->build_documents(
13439
                api_get_session_id(),
13440
                $this->get_course_int_id(),
13441
                true,
13442
                $itemList['document']
13443
            );
13444
        }
13445
13446
        if (isset($itemList['quiz'])) {
13447
            $courseBuilder->build_quizzes(
13448
                api_get_session_id(),
13449
                $this->get_course_int_id(),
13450
                true,
13451
                $itemList['quiz']
13452
            );
13453
        }
13454
13455
        require_once api_get_path(SYS_CODE_PATH).'forum/forumfunction.inc.php';
13456
13457
        /*if (!empty($itemList['thread'])) {
13458
            $postList = [];
13459
            foreach ($itemList['thread'] as $postId) {
13460
                $post = get_post_information($postId);
13461
                if ($post) {
13462
                    if (!isset($itemList['forum'])) {
13463
                        $itemList['forum'] = [];
13464
                    }
13465
                    $itemList['forum'][] = $post['forum_id'];
13466
                    $postList[] = $postId;
13467
                }
13468
            }
13469
13470
            if (!empty($postList)) {
13471
                $courseBuilder->build_forum_posts(
13472
                    $this->get_course_int_id(),
13473
                    null,
13474
                    null,
13475
                    $postList
13476
                );
13477
            }
13478
        }*/
13479
13480
        if (!empty($itemList['thread'])) {
13481
            $threadList = [];
13482
            $em = Database::getManager();
13483
            $repo = $em->getRepository('ChamiloCourseBundle:CForumThread');
13484
            foreach ($itemList['thread'] as $threadId) {
13485
                /** @var \Chamilo\CourseBundle\Entity\CForumThread $thread */
13486
                $thread = $repo->find($threadId);
13487
                if ($thread) {
13488
                    $itemList['forum'][] = $thread->getForumId();
13489
                    $threadList[] = $thread->getIid();
13490
                }
13491
            }
13492
13493
            if (!empty($threadList)) {
13494
                $courseBuilder->build_forum_topics(
13495
                    api_get_session_id(),
13496
                    $this->get_course_int_id(),
13497
                    null,
13498
                    $threadList
13499
                );
13500
            }
13501
        }
13502
13503
        $forumCategoryList = [];
13504
        if (isset($itemList['forum'])) {
13505
            foreach ($itemList['forum'] as $forumId) {
13506
                $forumInfo = get_forums($forumId);
13507
                $forumCategoryList[] = $forumInfo['forum_category'];
13508
            }
13509
        }
13510
13511
        if (!empty($forumCategoryList)) {
13512
            $courseBuilder->build_forum_category(
13513
                api_get_session_id(),
13514
                $this->get_course_int_id(),
13515
                true,
13516
                $forumCategoryList
13517
            );
13518
        }
13519
13520
        if (!empty($itemList['forum'])) {
13521
            $courseBuilder->build_forums(
13522
                api_get_session_id(),
13523
                $this->get_course_int_id(),
13524
                true,
13525
                $itemList['forum']
13526
            );
13527
        }
13528
13529
        if (isset($itemList['link'])) {
13530
            $courseBuilder->build_links(
13531
                api_get_session_id(),
13532
                $this->get_course_int_id(),
13533
                true,
13534
                $itemList['link']
13535
            );
13536
        }
13537
13538
        if (!empty($itemList['student_publication'])) {
13539
            $courseBuilder->build_works(
13540
                api_get_session_id(),
13541
                $this->get_course_int_id(),
13542
                true,
13543
                $itemList['student_publication']
13544
            );
13545
        }
13546
13547
        $courseBuilder->build_learnpaths(
13548
            api_get_session_id(),
13549
            $this->get_course_int_id(),
13550
            true,
13551
            [$this->get_id()],
13552
            false
13553
        );
13554
13555
        $courseBuilder->restoreDocumentsFromList();
13556
13557
        $zipFile = CourseArchiver::createBackup($courseBuilder->course);
13558
        $zipPath = CourseArchiver::getBackupDir().$zipFile;
13559
        $result = DocumentManager::file_send_for_download(
13560
            $zipPath,
13561
            true,
13562
            $this->get_name().'.zip'
13563
        );
13564
13565
        if ($result) {
13566
            api_not_allowed();
13567
        }
13568
13569
        return true;
13570
    }
13571
13572
    /**
13573
     * Get whether this is a learning path with the accumulated work time or not.
13574
     *
13575
     * @return int
13576
     */
13577
    public function getAccumulateWorkTime()
13578
    {
13579
        return (int) $this->accumulateWorkTime;
13580
    }
13581
13582
    /**
13583
     * Get whether this is a learning path with the accumulated work time or not.
13584
     *
13585
     * @return int
13586
     */
13587
    public function getAccumulateWorkTimeTotalCourse()
13588
    {
13589
        $table = Database::get_course_table(TABLE_LP_MAIN);
13590
        $sql = "SELECT SUM(accumulate_work_time) AS total
13591
                FROM $table
13592
                WHERE c_id = ".$this->course_int_id;
13593
        $result = Database::query($sql);
13594
        $row = Database::fetch_array($result);
13595
13596
        return (int) $row['total'];
13597
    }
13598
13599
    /**
13600
     * Set whether this is a learning path with the accumulated work time or not.
13601
     *
13602
     * @param int $value (0 = false, 1 = true)
13603
     *
13604
     * @return bool
13605
     */
13606
    public function setAccumulateWorkTime($value)
13607
    {
13608
        if (!api_get_configuration_value('lp_minimum_time')) {
13609
            return false;
13610
        }
13611
13612
        $this->accumulateWorkTime = (int) $value;
13613
        $table = Database::get_course_table(TABLE_LP_MAIN);
13614
        $lp_id = $this->get_id();
13615
        $sql = "UPDATE $table SET accumulate_work_time = ".$this->accumulateWorkTime."
13616
                WHERE c_id = ".$this->course_int_id." AND id = $lp_id";
13617
        Database::query($sql);
13618
13619
        return true;
13620
    }
13621
13622
    /**
13623
     * @param int $lpId
13624
     * @param int $courseId
13625
     *
13626
     * @return mixed
13627
     */
13628
    public static function getAccumulateWorkTimePrerequisite($lpId, $courseId)
13629
    {
13630
        $lpId = (int) $lpId;
13631
        $courseId = (int) $courseId;
13632
13633
        $table = Database::get_course_table(TABLE_LP_MAIN);
13634
        $sql = "SELECT accumulate_work_time
13635
                FROM $table
13636
                WHERE c_id = $courseId AND id = $lpId";
13637
        $result = Database::query($sql);
13638
        $row = Database::fetch_array($result);
13639
13640
        return $row['accumulate_work_time'];
13641
    }
13642
13643
    /**
13644
     * @param int $courseId
13645
     *
13646
     * @return int
13647
     */
13648
    public static function getAccumulateWorkTimeTotal($courseId)
13649
    {
13650
        $table = Database::get_course_table(TABLE_LP_MAIN);
13651
        $courseId = (int) $courseId;
13652
        $sql = "SELECT SUM(accumulate_work_time) AS total
13653
                FROM $table
13654
                WHERE c_id = $courseId";
13655
        $result = Database::query($sql);
13656
        $row = Database::fetch_array($result);
13657
13658
        return (int) $row['total'];
13659
    }
13660
13661
    /**
13662
     * In order to use the lp icon option you need to create the "lp_icon" LP extra field
13663
     * and put the images in.
13664
     *
13665
     * @return array
13666
     */
13667
    public static function getIconSelect()
13668
    {
13669
        $theme = api_get_visual_theme();
13670
        $path = api_get_path(SYS_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/';
13671
        $icons = ['' => get_lang('SelectAnOption')];
13672
13673
        if (is_dir($path)) {
13674
            $finder = new Finder();
13675
            $finder->files()->in($path);
13676
            $allowedExtensions = ['jpeg', 'jpg', 'png'];
13677
            /** @var SplFileInfo $file */
13678
            foreach ($finder as $file) {
13679
                if (in_array(strtolower($file->getExtension()), $allowedExtensions)) {
13680
                    $icons[$file->getFilename()] = $file->getFilename();
13681
                }
13682
            }
13683
        }
13684
13685
        return $icons;
13686
    }
13687
13688
    /**
13689
     * @param int $lpId
13690
     *
13691
     * @return string
13692
     */
13693
    public static function getSelectedIcon($lpId)
13694
    {
13695
        $extraFieldValue = new ExtraFieldValue('lp');
13696
        $lpIcon = $extraFieldValue->get_values_by_handler_and_field_variable($lpId, 'lp_icon');
13697
        $icon = '';
13698
        if (!empty($lpIcon) && isset($lpIcon['value'])) {
13699
            $icon = $lpIcon['value'];
13700
        }
13701
13702
        return $icon;
13703
    }
13704
13705
    /**
13706
     * @param int $lpId
13707
     *
13708
     * @return string
13709
     */
13710
    public static function getSelectedIconHtml($lpId)
13711
    {
13712
        $icon = self::getSelectedIcon($lpId);
13713
13714
        if (empty($icon)) {
13715
            return '';
13716
        }
13717
13718
        $theme = api_get_visual_theme();
13719
        $path = api_get_path(WEB_PUBLIC_PATH).'css/themes/'.$theme.'/lp_icons/'.$icon;
13720
13721
        return Display::img($path);
13722
    }
13723
13724
    /**
13725
     * @param string $value
13726
     *
13727
     * @return string
13728
     */
13729
    public function cleanItemTitle($value)
13730
    {
13731
        $value = Security::remove_XSS(strip_tags($value));
13732
13733
        return $value;
13734
    }
13735
13736
    public function setItemTitle(FormValidator $form)
13737
    {
13738
        if (api_get_configuration_value('save_titles_as_html')) {
13739
            $form->addHtmlEditor(
13740
                'title',
13741
                get_lang('Title'),
13742
                true,
13743
                false,
13744
                ['ToolbarSet' => 'TitleAsHtml', 'id' => uniqid('editor')]
13745
            );
13746
        } else {
13747
            $form->addText('title', get_lang('Title'), true, ['id' => 'idTitle', 'class' => 'learnpath_item_form']);
13748
            $form->applyFilter('title', 'trim');
13749
            $form->applyFilter('title', 'html_filter');
13750
        }
13751
    }
13752
13753
    /**
13754
     * @return array
13755
     */
13756
    public function getItemsForForm($addParentCondition = false)
13757
    {
13758
        $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
13759
        $course_id = api_get_course_int_id();
13760
13761
        $sql = "SELECT * FROM $tbl_lp_item
13762
                WHERE c_id = $course_id AND lp_id = ".$this->lp_id;
13763
13764
        if ($addParentCondition) {
13765
            $sql .= ' AND parent_item_id = 0 ';
13766
        }
13767
        $sql .= ' ORDER BY display_order ASC';
13768
13769
        $result = Database::query($sql);
13770
        $arrLP = [];
13771
        while ($row = Database::fetch_array($result)) {
13772
            $arrLP[] = [
13773
                'iid' => $row['iid'],
13774
                'id' => $row['iid'],
13775
                'item_type' => $row['item_type'],
13776
                'title' => $this->cleanItemTitle($row['title']),
13777
                'title_raw' => $row['title'],
13778
                'path' => $row['path'],
13779
                'description' => Security::remove_XSS($row['description']),
13780
                'parent_item_id' => $row['parent_item_id'],
13781
                'previous_item_id' => $row['previous_item_id'],
13782
                'next_item_id' => $row['next_item_id'],
13783
                'display_order' => $row['display_order'],
13784
                'max_score' => $row['max_score'],
13785
                'min_score' => $row['min_score'],
13786
                'mastery_score' => $row['mastery_score'],
13787
                'prerequisite' => $row['prerequisite'],
13788
                'max_time_allowed' => $row['max_time_allowed'],
13789
                'prerequisite_min_score' => $row['prerequisite_min_score'],
13790
                'prerequisite_max_score' => $row['prerequisite_max_score'],
13791
            ];
13792
        }
13793
13794
        return $arrLP;
13795
    }
13796
13797
    /**
13798
     * Gets whether this SCORM learning path has been marked to use the score
13799
     * as progress. Takes into account whether the learnpath matches (SCORM
13800
     * content + less than 2 items).
13801
     *
13802
     * @return bool True if the score should be used as progress, false otherwise
13803
     */
13804
    public function getUseScoreAsProgress()
13805
    {
13806
        // If not a SCORM, we don't care about the setting
13807
        if ($this->get_type() != 2) {
13808
            return false;
13809
        }
13810
        // If more than one step in the SCORM, we don't care about the setting
13811
        if ($this->get_total_items_count() > 1) {
13812
            return false;
13813
        }
13814
        $extraFieldValue = new ExtraFieldValue('lp');
13815
        $doUseScore = false;
13816
        $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
13817
        if (!empty($useScore) && isset($useScore['value'])) {
13818
            $doUseScore = $useScore['value'];
13819
        }
13820
13821
        return $doUseScore;
13822
    }
13823
13824
    /**
13825
     * Get the user identifier (user_id or username
13826
     * Depends on scorm_api_username_as_student_id in app/config/configuration.php.
13827
     *
13828
     * @return string User ID or username, depending on configuration setting
13829
     */
13830
    public static function getUserIdentifierForExternalServices()
13831
    {
13832
        if (api_get_configuration_value('scorm_api_username_as_student_id')) {
13833
            return api_get_user_info(api_get_user_id())['username'];
13834
        } elseif (api_get_configuration_value('scorm_api_extrafield_to_use_as_student_id') != null) {
13835
            $extraFieldValue = new ExtraFieldValue('user');
13836
            $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'));
13837
13838
            return $extrafield['value'];
13839
        } else {
13840
            return api_get_user_id();
13841
        }
13842
    }
13843
13844
    /**
13845
     * Save the new order for learning path items.
13846
     *
13847
     * We have to update parent_item_id, previous_item_id, next_item_id, display_order in the database.
13848
     *
13849
     * @param array $orderList A associative array with item ID as key and parent ID as value.
13850
     * @param int   $courseId
13851
     */
13852
    public static function sortItemByOrderList(array $orderList, $courseId = 0)
13853
    {
13854
        $courseId = $courseId ?: api_get_course_int_id();
13855
        $itemList = new LpItemOrderList();
13856
13857
        foreach ($orderList as $id => $parentId) {
13858
            $item = new LpOrderItem($id, $parentId);
13859
            $itemList->add($item);
13860
        }
13861
13862
        $parents = $itemList->getListOfParents();
13863
13864
        foreach ($parents as $parentId) {
13865
            $sameParentLpItemList = $itemList->getItemWithSameParent($parentId);
13866
            $previous_item_id = 0;
13867
            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...
13868
                $item_id = $sameParentLpItemList->list[$i]->id;
13869
                // display_order
13870
                $display_order = $i + 1;
13871
                $itemList->setParametersForId($item_id, $display_order, 'display_order');
13872
                // previous_item_id
13873
                $itemList->setParametersForId($item_id, $previous_item_id, 'previous_item_id');
13874
                $previous_item_id = $item_id;
13875
                // next_item_id
13876
                $next_item_id = 0;
13877
                if ($i < count($sameParentLpItemList->list) - 1) {
13878
                    $next_item_id = $sameParentLpItemList->list[$i + 1]->id;
13879
                }
13880
                $itemList->setParametersForId($item_id, $next_item_id, 'next_item_id');
13881
            }
13882
        }
13883
13884
        $table = Database::get_course_table(TABLE_LP_ITEM);
13885
13886
        foreach ($itemList->list as $item) {
13887
            $params = [];
13888
            $params['display_order'] = $item->display_order;
13889
            $params['previous_item_id'] = $item->previous_item_id;
13890
            $params['next_item_id'] = $item->next_item_id;
13891
            $params['parent_item_id'] = $item->parent_item_id;
13892
13893
            Database::update(
13894
                $table,
13895
                $params,
13896
                [
13897
                    'iid = ? AND c_id = ? ' => [
13898
                        (int) $item->id,
13899
                        (int) $courseId,
13900
                    ],
13901
                ]
13902
            );
13903
        }
13904
    }
13905
13906
    /**
13907
     * Get the depth level of LP item.
13908
     *
13909
     * @param array $items
13910
     * @param int   $currentItemId
13911
     *
13912
     * @return int
13913
     */
13914
    private static function get_level_for_item($items, $currentItemId)
13915
    {
13916
        $parentItemId = 0;
13917
        if (isset($items[$currentItemId])) {
13918
            $parentItemId = $items[$currentItemId]->parent;
13919
        }
13920
13921
        if ($parentItemId == 0) {
13922
            return 0;
13923
        } else {
13924
            return self::get_level_for_item($items, $parentItemId) + 1;
13925
        }
13926
    }
13927
13928
    /**
13929
     * Generate the link for a learnpath category as course tool.
13930
     *
13931
     * @param int $categoryId
13932
     *
13933
     * @return string
13934
     */
13935
    private static function getCategoryLinkForTool($categoryId)
13936
    {
13937
        $categoryId = (int) $categoryId;
13938
        $link = 'lp/lp_controller.php?'.api_get_cidreq().'&'
13939
            .http_build_query(
13940
                [
13941
                    'action' => 'view_category',
13942
                    'id' => $categoryId,
13943
                ]
13944
            );
13945
13946
        return $link;
13947
    }
13948
13949
    /**
13950
     * Return the scorm item type object with spaces replaced with _
13951
     * The return result is use to build a css classname like scorm_type_$return.
13952
     *
13953
     * @param $in_type
13954
     *
13955
     * @return mixed
13956
     */
13957
    private static function format_scorm_type_item($in_type)
13958
    {
13959
        return str_replace(' ', '_', $in_type);
13960
    }
13961
13962
    /**
13963
     * Check and obtain the lp final item if exist.
13964
     *
13965
     * @return learnpathItem
13966
     */
13967
    private function getFinalItem()
13968
    {
13969
        if (empty($this->items)) {
13970
            return null;
13971
        }
13972
13973
        foreach ($this->items as $item) {
13974
            if ($item->type !== 'final_item') {
13975
                continue;
13976
            }
13977
13978
            return $item;
13979
        }
13980
    }
13981
13982
    /**
13983
     * Get the LP Final Item Template.
13984
     *
13985
     * @return string
13986
     */
13987
    private function getFinalItemTemplate()
13988
    {
13989
        return file_get_contents(api_get_path(SYS_CODE_PATH).'lp/final_item_template/template.html');
13990
    }
13991
13992
    /**
13993
     * Get the LP Final Item Url.
13994
     *
13995
     * @return string
13996
     */
13997
    private function getSavedFinalItem()
13998
    {
13999
        $finalItem = $this->getFinalItem();
14000
        $doc = DocumentManager::get_document_data_by_id(
14001
            $finalItem->path,
14002
            $this->cc
14003
        );
14004
        if ($doc && file_exists($doc['absolute_path'])) {
14005
            return file_get_contents($doc['absolute_path']);
14006
        }
14007
14008
        return '';
14009
    }
14010
}
14011