Passed
Push — 1.11.x ( e32612...1f11a0 )
by Julito
13:34
created

learnpath   F

Complexity

Total Complexity 1933

Size/Duplication

Total Lines 14214
Duplicated Lines 0 %

Importance

Changes 10
Bugs 0 Features 0
Metric Value
eloc 7883
c 10
b 0
f 0
dl 0
loc 14214
rs 0.8
wmc 1933

227 Methods

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

How to fix   Complexity   

Complex Class

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

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

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

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

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

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

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
7860
                if ($action != 'add') {
7861
                    if ($arrLP[$i]['item_type'] == 'dir' &&
7862
                        !in_array($arrLP[$i]['id'], $arrHide) &&
7863
                        !in_array($arrLP[$i]['parent_item_id'], $arrHide)
7864
                    ) {
7865
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7866
                    } else {
7867
                        $arrHide[] = $arrLP[$i]['id'];
7868
                    }
7869
                } else {
7870
                    if ($arrLP[$i]['item_type'] == 'dir') {
7871
                        $return .= '<option '.(($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '').'style="padding-left:'.($arrLP[$i]['depth'] * 10).'px;" value="'.$arrLP[$i]['id'].'">'.$arrLP[$i]['title'].'</option>';
7872
                    }
7873
                }
7874
            }
7875
            reset($arrLP);
7876
        }
7877
7878
        $return .= '</select>';
7879
        $return .= '</td>';
7880
        $return .= '</tr>';
7881
        $return .= '<tr>';
7882
        $return .= '<td class="label"><label for="previous">'.get_lang('Position').' :</label></td>';
7883
        $return .= '<td class="input">';
7884
        $return .= '<select id="previous" name="previous" size="1">';
7885
        $return .= '<option class="top" value="0">'.get_lang('FirstPosition').'</option>';
7886
7887
        for ($i = 0; $i < count($arrLP); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

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

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

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