Passed
Push — 1.11.x ( 5d346a...3541c6 )
by Julito
14:56
created

learnpath   F

Complexity

Total Complexity 1911

Size/Duplication

Total Lines 14088
Duplicated Lines 0 %

Importance

Changes 6
Bugs 0 Features 1
Metric Value
eloc 7800
c 6
b 0
f 1
dl 0
loc 14088
rs 0.8
wmc 1911

226 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

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